Запрос HDF5 в Pandas

У меня есть следующие данные (18 619 211 строк), хранящиеся в качестве объекта dataframe pandas в файле hdf5:

             date    id2         w
id                              
100010 1980-03-31   10401  0.000839
100010 1980-03-31   10604  0.020140
100010 1980-03-31   12490  0.026149
100010 1980-03-31   13047  0.033560
100010 1980-03-31   13303  0.001657

где id - индекс, а другие - столбцы. date - np.datetime64. Мне нужно выполнить такой запрос (код, конечно, не работает):

db=pd.HDFStore('database.h5')
data=db.select('df', where='id==id_i & date>bgdt & date<endt')

Примечание id_i, bgdt, endt - это все переменные, а не фактические значения и должны быть переданы в цикле. например:

dates - это индекс периода Панды или индекс временных меток, в любом случае, я могу конвертироваться друг в друга.

dates=['1990-01', 1990-04','1990-09',......]  

id_list - это список идентификаторов

id_list=[100010, 100011,1000012,.......]

Цикл выглядит так (причина, по которой я делаю цикл, состоит в том, что данные огромны, есть и другие наборы данных, у меня есть запрос в одно и то же время, а затем выполняются некоторые операции)

db=pd.HDFStore('database.h5')
for id_i in id_list:
    for date in dates:
        bgdt=date-1 (move to previous month)
        endt=date-60 (previous 60 month)
        data=db.select('df', where='index==id_i & date>bgdt & date<endt')
        ......

Эта проблема состоит из двух частей:

  • Я не знаю, как запросить индекс и столбцы в одно и то же время. Документ в pandas показал, как запросить на основе условий индекса или условий столбцов, но нет примеров того, как на основе запросов запрашивать их в ОДНОМ ВРЕМЕНИ.
    • (Кстати, это очень распространено в документации по Pandas. Док обычно показывает очень простую вещь, например, как делать "A", или как делать "B", но не как делать BOTH "A" и "B". Пример - это использование query на мультидюймовом фрейме pandas. Документ отображается на основе level=0 OR level=1, но нет примера о том, как делать BOTH в ОДНОМ ВРЕМЕНИ.)
  • Я не знаю, как передать три id_i, bgdt, endt в запрос. Я знаю, как пройти только с помощью %s, но не всех из них.
    • Я также немного запутался с типом datetime. Кажется, что довольно много времени: datetime.datetime, numpy.datetime64, pandas.Period. Я в основном работаю над ежемесячными данными, поэтому pandas.Period является наиболее полезным. Но я не могу легко преобразовать столбец (не индекс) временных меток (тип даты по умолчанию для Pandas при анализе из необработанных данных). Есть ли какой-то тип данных, который является просто "датой", а не меткой времени, а не периодом, а просто простой ДАТА с только годом, месяцем и днем?

Много неприятностей, но я действительно ЛЮБЛЮ питон и панды (я пытаюсь переместить мой рабочий процесс с SAS на Python). Любая помощь будет оценена!

Ответ 1

здесь представлены документы для запросов по неиндексным столбцам.

Создайте тестовые данные. Неясно, как создается исходный кадр, например, его уникальные данные и диапазоны, поэтому я создал образец с 10-миллиметровыми строками и многоуровневым диапазоном дат с столбцом id.

In [60]: np.random.seed(1234)

In [62]: pd.set_option('display.max_rows',20)

In [63]: index = pd.MultiIndex.from_product([np.arange(10000,11000),pd.date_range('19800101',periods=10000)],names=['id','date'])

In [67]: df = DataFrame(dict(id2=np.random.randint(0,1000,size=len(index)),w=np.random.randn(len(index))),index=index).reset_index().set_index(['id','date'])

In [68]: df
Out[68]: 
                  id2         w
id    date                     
10000 1980-01-01  712  0.371372
      1980-01-02  718 -1.255708
      1980-01-03  581 -1.182727
      1980-01-04  202 -0.947432
      1980-01-05  493 -0.125346
      1980-01-06  752  0.380210
      1980-01-07  435 -0.444139
      1980-01-08  128 -1.885230
      1980-01-09  425  1.603619
      1980-01-10  449  0.103737
...               ...       ...
10999 2007-05-09    8  0.624532
      2007-05-10  669  0.268340
      2007-05-11  918  0.134816
      2007-05-12  979 -0.769406
      2007-05-13  969 -0.242123
      2007-05-14  950 -0.347884
      2007-05-15   49 -1.284825
      2007-05-16  922 -1.313928
      2007-05-17  347 -0.521352
      2007-05-18  353  0.189717

[10000000 rows x 2 columns]

Запишите данные на диск, указав, как создать столбец данных (обратите внимание, что индексы автоматически запрашиваются, что позволяет также запрашивать id2). Это де-факто эквивалентно выполнению. Это позаботится об открытии и закрытии магазина (вы можете сделать то же самое, открыв магазин, добавив и закрыв).

Чтобы запросить столбец, он ДОЛЖЕН БЫТЬ КОЛОНКОЙ ДАННЫХ или индексом фрейма.

In [70]: df.to_hdf('test.h5','df',mode='w',data_columns=['id2'],format='table')

In [71]: !ls -ltr test.h5
-rw-rw-r-- 1 jreback users 430540284 May 26 17:16 test.h5

Запросы

In [80]: ids=[10101,10898]

In [81]: start_date='20010101'

In [82]: end_date='20010301'

Вы можете указать даты как строку (либо в строке, либо как переменные, вы также можете указать объекты, похожие на Timestamp)

In [83]: pd.read_hdf('test.h5','df',where='date>start_date & date<end_date')
Out[83]: 
                  id2         w
id    date                     
10000 2001-01-02  972 -0.146107
      2001-01-03  954  1.420412
      2001-01-04  567  1.077633
      2001-01-05   87 -0.042838
      2001-01-06   79 -1.791228
      2001-01-07  744  1.110478
      2001-01-08  237 -0.846086
      2001-01-09  998 -0.696369
      2001-01-10  266 -0.595555
      2001-01-11  206 -0.294633
...               ...       ...
10999 2001-02-19  616 -0.745068
      2001-02-20  577 -1.474748
      2001-02-21  990 -1.276891
      2001-02-22  939 -1.369558
      2001-02-23  621 -0.214365
      2001-02-24  396 -0.142100
      2001-02-25  492 -0.204930
      2001-02-26  478  1.839291
      2001-02-27  688  0.291504
      2001-02-28  356 -1.987554

[58000 rows x 2 columns]

Вы можете использовать встроенные списки

In [84]: pd.read_hdf('test.h5','df',where='date>start_date & date<end_date & id=ids')
Out[84]: 
                  id2         w
id    date                     
10101 2001-01-02  722  1.620553
      2001-01-03  849 -0.603468
      2001-01-04  635 -1.419072
      2001-01-05  331  0.521634
      2001-01-06  730  0.008830
      2001-01-07  706 -1.006412
      2001-01-08   59  1.380005
      2001-01-09  689  0.017830
      2001-01-10  788 -3.090800
      2001-01-11  704 -1.491824
...               ...       ...
10898 2001-02-19  530 -1.031167
      2001-02-20  652 -0.019266
      2001-02-21  472  0.638266
      2001-02-22  540 -1.827251
      2001-02-23  654 -1.020140
      2001-02-24  328 -0.477425
      2001-02-25  871 -0.892684
      2001-02-26  166  0.894118
      2001-02-27  806  0.648240
      2001-02-28  824 -1.051539

[116 rows x 2 columns]

Вы также можете указать логические выражения

In [85]: pd.read_hdf('test.h5','df',where='date>start_date & date<end_date & id=ids & id2>500 & id2<600')
Out[85]: 
                  id2         w
id    date                     
10101 2001-01-12  534 -0.220692
      2001-01-14  596 -2.225393
      2001-01-16  596  0.956239
      2001-01-30  513 -2.528996
      2001-02-01  572 -1.877398
      2001-02-13  569 -0.940748
      2001-02-14  541  1.035619
      2001-02-21  571 -0.116547
10898 2001-01-16  591  0.082564
      2001-02-06  586  0.470872
      2001-02-10  531 -0.536194
      2001-02-16  586  0.949947
      2001-02-19  530 -1.031167
      2001-02-22  540 -1.827251

Чтобы ответить на ваш реальный вопрос, я бы сделал это (их на самом деле недостаточно информации, но я поставлю некоторые разумные ожидания):

  • Не перекликайтесь с запросами, если у вас нет очень небольшого количества абсолютных запросов
  • Прочтите самый большой кусок в память, который вы можете. Обычно это достигается путем выбора самых больших диапазонов данных, которые вам нужны, даже если вы выберете БОЛЬШЕ данных, чем вам действительно нужно.
  • Затем подзапросите использование выражений в памяти, которые, как правило, будут на порядок быстрее.
  • Элементы списка ограничены примерно 30 элементами (это текущий предел реализации на стороне PyTables). Он будет работать, если вы укажете больше, но что произойдет, так это то, что вы будете читать много данных, тогда оно будет переиндексировано вниз (в памяти). Поэтому пользователь должен знать об этом.

Например, скажем, что у вас 1000 уникальных идентификаторов со 10000 датами, как показывает мой пример. Вы хотите выбрать 200 из них, с диапазоном дат 1000.

Поэтому в этом случае я просто выберет на датах, а затем сравним в памяти, примерно так:

df = pd.read_hdf('test.h5','df',where='date=>global_start_date & date<=global_end_date')
df[df.isin(list_of_ids)]

У вас также могут быть даты, которые изменяются за идентификаторы. Так что купите их, на этот раз используя список идентификаторов.

Что-то вроде этого:

output = []
for i in len(list_of_ids) % 30:
    ids = list_of_ids[i:(i+30)]
    start_date = get_start_date_for_these_ids (global)
    end_date = get_end_date_for_these_ids (global)
    where = 'id=ids & start_date>=start_date & end_date<=end_date'
    df = pd.read_hdf('test.h5','df',where=where)
    output.append(df)

 final_result = concat(output)

Основная идея состоит в том, чтобы выбрать надмножество данных, используя критерии, которые вы хотите, подвыбор, чтобы он соответствовал памяти, но вы ограничиваете количество запросов, которые вы делаете (например, представьте, что вы в конечном итоге выбираете одну строку с помощью своих запрос, если вам нужно запросить это 18M раз, что плохо).