Pandas HDFStore извлекает понимание соответствия данных - PullRequest
1 голос
/ 02 июня 2019

Итак, сначала было похоже, что я столкнулся с другой ошибкой, но в конце всех тестов я совершенно не уверен, что это действительно ошибка, и что я понимаю, как построить конвейер обработки данных с HDF-пандами.

Садись, давай поедем вместе.Надеюсь, что вы можете уточнить для меня что-то в конце.



Подготовка

SIZE_0 = 10**6
SIZE_1 = 4
df = pd.DataFrame(np.random.rand(SIZE_0, SIZE_1))
print (df.head())
          0         1         2         3
0  0.327362  0.084638  0.124322  0.116745
1  0.606545  0.484079  0.977239  0.120613
2  0.014407  0.973912  0.464409  0.959907
3  0.357551  0.641503  0.889408  0.776769
4  0.770845  0.548562  0.587054  0.569719

Положить в магазин на 2 части

cols1 = list(df.columns[:SIZE_1//2])
cols2 = list(df.columns[SIZE_1//2:])
with pd.HDFStore('test.h5') as store:
    store.put('df1', df[cols1], 't')
    store.put('df2', df[cols2], 't')


Теперь к проблеме.Чтение целого df из HDFStore с select_as_multiple на намного медленнее, чем select:

%%time
with pd.HDFStore('test.h5') as store:
    out = store.select_as_multiple(['df1', 'df2'])
print (out.shape)
(1000000, 4)
CPU times: user 24.3 s, sys: 38.6 ms, total: 24.3 s
Wall time: 24.3 s

И обычное select:

%%time
with pd.HDFStore('test.h5') as store:
    df1 = store.select('df1')
    df2 = store.select('df2')
    out = pd.concat([df1, df2], axis=1)
print (out.shape)
(1000000, 4)
CPU times: user 48.1 ms, sys: 23.9 ms, total: 72 ms
Wall time: 68.3 ms

Так что в этот момент я собирался опубликовать это как проблему с производительностью, но после некоторой первоначальной «тупой проверки» (как я и думал) я получаю неожиданные (по крайней мере для меня) результаты.



Давайте увеличим количество столбцов и посмотрим, что произойдет.

SIZE_1 = 8
df = pd.DataFrame(np.random.rand(SIZE_0, SIZE_1))

cols1 = list(df.columns[:SIZE_1//2])
cols2 = list(df.columns[SIZE_1//2:])
with pd.HDFStore('test.h5') as store:
    store.put('df1', df[cols1], 't')
    store.put('df2', df[cols2], 't')

Теперь, используя тот же код для select_as_multiple, мы получим вывод:

(1000000, 8)
CPU times: user 14.7 s, sys: 87.3 ms, total: 14.8 s
Wall time: 14.8 s

Странные вещи.Мы увеличили размер данных вдвое, но время на стене теперь 10s меньше.

В то же время код для извлечения df на select выполняется немного медленнее:

(1000000, 8)
CPU times: user 90.6 ms, sys: 27.9 ms, total: 119 ms
Wall time: 115 ms


Ну, после этого я не смог остановить свое любопытство и сделать еще один пробный снимок :).Теперь с созданием экземпляра df с помощью SIZE_1 = 16 (снова все остальные строки кода остаются неизменными - не будем копировать их здесь снова для краткости).

Теперь select_as_multiple работает даже быстрее :

(1000000, 16)
CPU times: user 8.27 s, sys: 184 ms, total: 8.45 s
Wall time: 8.45 s

Но для простого select все как и ожидалось - время выполнения увеличилось:

(1000000, 16)
CPU times: user 181 ms, sys: 124 ms, total: 306 ms
Wall time: 302 ms

Но в то же время select все еще работает очень- намного быстрее.



Напоследок на вопросы:

1.Почему `select_as_multiple` работает так плохо по сравнению с` select`?

Кстати, это проблема не только выбора без указания условия where:

%%time
with pd.HDFStore('test.h5') as store:
    out = store.select_as_multiple(['df1', 'df2'], where='index < 500000')
print (out.shape)
(500000, 16)
CPU times: user 4.65 s, sys: 56.7 ms, total: 4.7 s
Wall time: 4.69 s

И дляselect:

%%time
with pd.HDFStore('test.h5') as store:
    df1 = store.select('df1', where='index < 500000')
    df2 = store.select('df2', where='index < 500000')
    out = pd.concat([df1, df2], axis=1)
print (out.shape)
(500000, 16)
CPU times: user 871 ms, sys: 89 ms, total: 960 ms
The decreasing of time (with such particular `where`) should be more expected, since we have to 
Wall time: 927 ms

Все еще намного быстрее.Но можно отметить , что where уменьшает время на select_as_multiple и в то же время увеличивает на select.Итак, вот еще один вопрос:


2.Зачем указывать выражение `where` _reduces_ time для` select_as_multiple` и в то же время _increases_ его для `select`?

Ожидаемое поведение и увеличивается, или уменьшается для конкретного where.Но не напротив друг друга.


3.Почему увеличение размера данных в направлении оси = 1 уменьшает время чтения `select_as_multiple`?

Мы увеличиваем размер данных, но выбор выполняется почти в несколько раз быстрее?Это странно.Может быть, это особенность дизайна, которая гласит - Не используйте хранилище HDF, пока у вас не будет действительно большого числа столбцов в вашем df ?Но я не мог вспомнить что-то подобное из документов .Только противоположные варианты использования - именно в разделе с select_as_multiple, который предлагает разделить ваши данные на столбцы 'запроса' и 'другие' (таким образом уменьшите количество столбцов в хранимых dfs), чтобы ускорить запросы.


Давайте проведем еще больше тестов.

SIZE_0 = 10**6 и SIZE_1 = 16:

%%time
with pd.HDFStore('test.h5') as store:
    out = store.select_as_multiple(['df1', 'df2'])
print (out.shape)
(1000000, 16)
CPU times: user 8.39 s, sys: 232 ms, total: 8.62 s
Wall time: 8.64 s

Увеличение размера dfпо оси = 0 дважды SIZE_0 = 2*10**6 и SIZE_1 = 16:

%%time
with pd.HDFStore('test.h5') as store:
    out = store.select_as_multiple(['df1', 'df2'])
print (out.shape)
(2000000, 16)
CPU times: user 32.3 s, sys: 370 ms, total: 32.6 s
Wall time: 32.6 s

По сравнению с увеличением размера df по оси = 1 дважды SIZE_0 = 10**6 иSIZE_1 = 2*16:

%%time
with pd.HDFStore('test.h5') as store:
    out = store.select_as_multiple(['df1', 'df2'])
print (out.shape)
(1000000, 32)
CPU times: user 9.05 s, sys: 384 ms, total: 9.43 s
Wall time: 9.43 s

Итак 32s v 10s.

4.Это означает, что для хранения HDF гораздо эффективнее добавлять столбцы вместо строк?!

Это действительно сбивает с толку.Разве это не неправильно?Насколько я понимаю, PyTables так же, как pandas HDF является «ориентированным на строки»?


В последнем тесте можно было заметить, что мы, наконец, увеличили время выполнения после расширения наших данных в axis=1направление.Давайте выясним, когда именно это стало правдой:

SIZE_0 = 10**6
SIZE_1s = list(range(4, 40)) # List of SIZE_1 to iterate

# Iterating
timings = []
for SIZE_1 in SIZE_1s:
    df = pd.DataFrame(np.random.rand(SIZE_0, SIZE_1))

    cols1 = list(df.columns[:SIZE_1//2])
    cols2 = list(df.columns[SIZE_1//2:])
    with pd.HDFStore('test.h5') as store:
        # Put to store
        store.put('df1', df[cols1], 't')
        store.put('df2', df[cols2], 't')
        # Read from store, note the time
        start = pd.Timestamp.now()
        out = store.select_as_multiple(['df1', 'df2'])
        # Appending timings
        timings.append((pd.Timestamp.now()-start).total_seconds())
# Plotting
to_plot = pd.DataFrame(timings,
                       index=pd.Index(SIZE_1s, name='column_C'),
                       columns=['read time'])
_ = to_plot.plot(figsize = (14, 6),
                 title = 'DF read time from HDF store by DFs column count',
                 color = 'blue')

OUTPUT

Итак, начальная динамика (до 23-24 столбцов) проста -больше столбцов = более быстрое чтение.

5.Является ли число столбцов 24 (мы должны разделить здесь для 2 df, то есть около 12 столбцов) что-то вроде порога проектирования?И только после достижения этого следует подумать об использовании HDF store?



Некоторая информация о системе:

pd.__version__
tables.__version__
'0.24.2'
'3.5.1'

Также есть 24GB, установленная на 64-немного Ubuntu 19.04.И в то же время самый большой из использованных dfs в тестах был размером 300MB.Так что это не должно было создать никаких проблем.

РЕДАКТИРОВАТЬ: , так как не было дано никакого объяснения - я открыл выпуск .

...