Прожорливость индексов датафрейма, о которой следует знать каждому
Несколько простых рекомендаций работы с индексами датафрейма помогут сэкономить значительную часть памяти и, возможно, приведут к повышению скорости его обработки.
Создадим два датафрейма и посмотрим, какие индексы для них создает система:
import pandas as pd import numpy as np import sys np.random.seed(0) df1 = pd.DataFrame([[1, 'first'], [2, 'second'], [3, 'third']]) df2 = pd.DataFrame([[4, 'fourth'], [5, 'fifth'], [6, 'sixth']]) df1.index, df2.index
Конструкция RangeIndex обозначает границы индекса и шаг. Она предназначена для экономии памяти и ускорения работы с датафреймом. Однако, если создавать индексы вручную и проводить с ними операции система создает более тяжеловесную конструкцию Int64Index и др.
Например, вот индекс после конкатенации наших массивов:
df = pd.concat([df1, df2]) df.index
Если мы создадим аналогичные индексы сами, все равно получим Int64Index. При этом, даже если они будут идти последовательно, при конкатенации не будет присвоен RangeIndex:
df1 = pd.DataFrame([[1, 'first'], [2, 'second'], [3, 'third']], index=[1,2,3]) df2 = pd.DataFrame([[4, 'fourth'], [5, 'fifth'], [6, 'sixth']], index=[4,5,6]) df1.index, df2.index
df = pd.concat([df1, df2]) df.index
А вот индекс можно сбросить и так получить конструкцию RangeIndex:
df.reset_index().index
Того же эффекта можно достичь при конкатенации, если в методе указать параметр ignore_index=True:
df = pd.concat([df1, df2], ignore_index=True) df.index
Теперь сгенерируем большие датафреймы и посмотрим, как влияет тип индекса на память:
rands_ar1 = np.random.randint(10, size=(10000000, 5)) rands_ar2 = np.random.randint(10, size=(10000000, 5)) df1 = pd.DataFrame(rands_ar1, columns= ['1', '2', '3', '4', '5']) df2 = pd.DataFrame(rands_ar2, columns= ['1', '2', '3', '4', '5'])
df_all1 = pd.concat([df1, df2]) df_all1.info()
Если же конкатенировать с ignore_index=True:
df_all2 = pd.concat([df1, df2], ignore_index=True) df_all2.info()
Почти вся разница (150-160 МБ) - это вклад размера индекса:
sys.getsizeof(df_all2.index), sys.getsizeof(df_all1.index)
Особо хочу отметить, что даже сжатие в популярный формат parquet очень слабо влияет на размер индекса:
df_all1.to_parquet('data/df_all1.parquet') df_all2.to_parquet('data/df_all2.parquet')
Хоть разница сократилась, она достаточно велика - 80 МБ, в то же время сам датафрейм стал весить чуть более 50 МБ. То есть индекс занимает почти в 2 раза больше места, чем сам датафрейм!
Выводы, если значения индекса вам не принципиальны: