Есть ли какой-нибудь элегантный способ определить фрейм данных со столбцом массива dtype? - программирование
Подтвердить что ты не робот

Есть ли какой-нибудь элегантный способ определить фрейм данных со столбцом массива dtype?

Я хочу обработать данные уровня 2 запаса в пандах. Предположим, что для простоты в каждой строке четыре вида данных:

  • миллис: timestamp, int64
  • last_price: цена последней сделки, float64,
  • ask_queue: объем запрашиваемой стороны, массив фиксированного размера (200) из int32
  • bid_queue: объем стороны предложения, массив фиксированного размера (200) из int32

Который может быть легко определен как структурированный dtype в numpy:

dtype = np.dtype([
   ('millis', 'int64'), 
   ('last_price', 'float64'), 
   ('ask_queue', ('int32', 200)), 
   ('bid_queue', ('int32', 200))
])

И таким образом, я могу получить доступ к ask_queue и bid_queue например:

In [17]: data = np.random.randint(0, 100, 1616 * 5).view(dtype)

% compute the average of ask_queue level 5 ~ 10
In [18]: data['ask_queue'][:, 5:10].mean(axis=1)  
Out[18]: 
array([33.2, 51. , 54.6, 53.4, 15. , 37.8, 29.6, 58.6, 32.2, 51.6, 34.4,
       43.2, 58.4, 26.8, 54. , 59.4, 58.8, 38.8, 35.2, 71.2])

Мой вопрос заключается в том, как определить DataFrame включить данные?

Здесь есть два решения:

A. установите ask_queue и bid_queue как два столбца со значениями массива следующим образом:

In [5]: df = pd.DataFrame(data.tolist(), columns=data.dtype.names)

In [6]: df.dtypes
Out[6]: 
millis          int64
last_price    float64
ask_queue      object
bid_queue      object
dtype: object

Однако в этом решении есть как минимум две проблемы:

  1. ask_queue и bid_queue потеряли dtype 2D-массив и все удобные методы;
  2. Производительность, поскольку она становится массивом объектов, а не двумерным массивом.

Б. ask_queue bid_quene ask_queue и bid_quene до 2 * 200 столбцов:

In [8]: ntype = np.dtype([('millis', 'int64'), ('last_price', 'float64')] + 
   ...:                  [(f'{name}{i}', 'int32') for name in ['ask', 'bid'] for i in range(200)])

In [9]: df = pd.DataFrame.from_records(data.view(ntype))

In [10]: df.dtypes
Out[10]: 
millis          int64
last_price    float64
ask0            int32
ask1            int32
ask2            int32
ask3            int32
ask4            int32
ask5            int32
...

Это лучше, чем решение А. Но столбцы 2 * 200 выглядят избыточными.

Есть ли какое-нибудь решение, которым можно воспользоваться как структурированным dtype в numpy? Интересно, может ли ExtensionArray или ExtensionDtype решить эту проблему?

4b9b3361

Ответ 1

Q: Есть ли какое-нибудь решение, которое может использовать преимущество как структурированный dtype в numpy?

Работа с данными L2-DoM сопряжена с двумя сложностями по сравнению с данными о ценовой подаче ToB (Top-of-the-Book). а) собственный фид быстрый (очень быстрый/протокол FIX или другие частные фиды данных доставляют записи с сотнями, тысячами (больше во время фундаментальных событий на основных) L2-DoM изменяется в миллисекунду. Обработка и хранение должны быть ориентированы на производительность b ) любого рода в автономном режиме анализа была успешно манипулировать и эффективно обрабатывать большие наборы данных, в связи с характером пункта а)

  • Настройки хранения
  • Использование numpy -alike синтаксических предпочтения
  • Настройки производительности

Настройки хранения: решено

Учитывая, что pandas.DataFrame был установлен в качестве предпочтительного типа хранилища, pandas.DataFrame это, хотя синтаксис и предпочтения производительности могут иметь неблагоприятные последствия.

Можно пойти другим путем, но может привести к неизвестным затратам на рефакторинг/реинжиниринг, которые операционная среда O/P не должна или уже не желает нести.

Сказав это, ограничения возможностей pandas должны быть учтены при проектировании, и все остальные шаги должны будут соответствовать этому, если это предпочтение не будет пересмотрено в будущем.


Синтаксис numpy -alike: решен

Этот запрос ясен и понятен, так как numpy и быстрые инструменты созданы для высокопроизводительного анализа чисел. Учитывая предпочтения набора для хранения, мы будем реализовывать пару numpy -tricks так, чтобы вписаться в pandas 2D- DataFrame все при разумных затратах на оба .STORE и .RETRIEVE направлений:

 # on .STORE:
 testDF['ask_DoM'][aRowIDX] = ask200.dumps()      # type(ask200) <class 'numpy.ndarray'>

 # on .RETRIEVE:
 L2_ASK = np.loads( testDF['ask_DoM'][aRowIDX] )  # type(L2_ASK) <class 'numpy.ndarray'>

Настройки производительности: ТЕСТИРОВАНИЕ

Чистые дополнительные расходы на предлагаемое решение для обоих направлений .STORE и .RETRIEVE были протестированы на:

.STORE стоимость .STORE направления .STORE составляет не менее 70 [us] и не более ~ 160 [us] на ячейку для заданных масштабов массивов L2_DoM (среднее значение: 78 [ms] StDev: 9-11 [ms]):

>>> [ f( [testDUMPs() for _ in range(1000)] ) for f in (np.min,np.mean,np.std,np.max) ]
[72, 79.284, 11.004153942943548, 150]
[72, 78.048, 10.546135548152224, 160]
[71, 78.584,  9.887971227708949, 139]
[72, 76.9,    8.827332496286745, 132]

Повторяющаяся стоимость в направлении .RETRIEVE составляет не менее 46 [us] и не более ~ 123 [us] на ячейку для заданных масштабов массивов L2_DoM (avg: 50 [us] StDev: 9.5 [us]):

>>> [ f( [testLOADs() for _ in range(1000)] ) for f in (np.min,np.mean,np.std,np.max) ]
[46, 50.337, 9.655194197943405, 104]
[46, 49.649, 9.462272665697178, 123]
[46, 49.513, 9.504293766503643, 123]
[46, 49.77,  8.367165350344164, 114]
[46, 51.355, 6.162434583831296,  89]

Можно ожидать еще более высокой производительности, если использовать более int64 типы данных int64 выровненные по int64 (да, за счет удвоенных затрат на хранение, но при этом затраты на вычисления будут определять, будет ли это движение иметь преимущество в производительности) и благодаря возможности использовать memoryview [ CN03] манипуляции, которые могут перерезать горло и сократить время задержки до 22 [us].


Тест проводился под py3.5.6, numpy v1.15.2, используя:

>>> import numpy as np; ask200 = np.arange( 200, dtype = np.int32 ); s = ask200.dumps()
>>> from zmq import Stopwatch; aClk = Stopwatch()
>>> def testDUMPs():
...     aClk.start()
...     s = ask200.dumps()
...     return aClk.stop()
... 
>>> def testLOADs():
...     aClk.start()
...     a = np.loads( s )
...     return aClk.stop()
...

Процессор платформы, иерархия кэша и детали оперативной памяти:

>>> get_numexpr_cpuinfo_details_on_CPU()

'TLB size'______________________________:'1536 4K pages'
'address sizes'_________________________:'48 bits physical, 48 bits virtual'
'apicid'________________________________:'17'
'bogomips'______________________________:'7199.92'
'bugs'__________________________________:'fxsave_leak sysret_ss_attrs null_seg spectre_v1 spectre_v2'
'cache size'____________________________:'2048 KB'
'cache_alignment'_______________________:'64'
'clflush size'__________________________:'64'
'core id'_______________________________:'1'
'cpu MHz'_______________________________:'1400.000'
'cpu cores'_____________________________:'2'
'cpu family'____________________________:'21'
'cpuid level'___________________________:'13'
'flags'_________________________________:'fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc extd_apicid aperfmperf eagerfpu pni pclmulqdq monitor ssse3 cx16 sse4_1 sse4_2 popcnt aes xsave avx lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs xop skinit wdt lwp fma4 nodeid_msr topoext perfctr_core perfctr_nb cpb hw_pstate vmmcall arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold'
'fpu'___________________________________:'yes'
'fpu_exception'_________________________:'yes'
'initial apicid'________________________:'1'
'microcode'_____________________________:'0x6000626'
'model'_________________________________:'1'
'model name'____________________________:'AMD FX(tm)-4100 Quad-Core Processor'
'physical id'___________________________:'0'
'power management'______________________:'ts ttp tm 100mhzsteps hwpstate cpb'
'processor'_____________________________:'1'
'siblings'______________________________:'4'
'stepping'______________________________:'2'
'vendor_id'_____________________________:'AuthenticAMD'
'wp'____________________________________:'yes'

Ответ 2

Pandas был разработан для обработки и обработки двумерных данных (вид, который вы бы поместили в электронную таблицу). Поскольку "ask_queue" и "bid_queue" не являются одномерными сериями, а являются двумерными массивами, вы не можете (легко) поместить их в кадр данных Pandas.

В таких случаях вы должны использовать другие библиотеки, такие как xarray: http://xarray.pydata.org/

import xarray as xr

# Creating variables, first argument is the name of the dimensions
last_price = xr.Variable("millis", data["last_price"])
ask_queue = xr.Variable(("millis", "levels"), data["ask_queue"])
bid_queue = xr.Variable(("millis", "levels"), data["bid_queue"])

# Putting the variables in a dataset, the multidimensional equivalent of a Pandas
# dataframe
ds = xr.Dataset({"last_price": last_price, "ask_queue": ask_queue,
                 "bid_queue": bid_queue}, coords={"millis": data["millis"]})

# Computing the average of ask_queue level 5~10
ds["ask_queue"][{"levels": slice(5,10)}].mean(axis=1)