Более быстрый способ выполнить эту задачу Pandas, чем при использовании Apply для большого набора данных?
У меня есть большой набор данных CSV файлов, состоящий из двух разных объектов: "object_a" и "object_b". Каждое из этих объектов также имеет числовое значение "галочка".
Type, Parent Name, Ticks
object_a, 4556421, 34
object_a, 4556421, 0
object_b, 4556421, 0
object_a, 3217863, 2
object_b, 3217863, 1
......
Каждый объект имеет значение "Родительское имя", поэтому в большинстве случаев один из каждого объекта будет иметь значение "Родительское имя", но это не всегда так.
У меня есть две цели с этим набором данных:
-
извлечь все object_a под родительским именем, где i) есть > 1 object_a и; ii) объект_a имеет 0 тиков, но у другого объекта object_a есть > 0 тиков. т.е. только с нулевыми тиками
-
извлечь все object_b под родительским именем, где i) существует >= 1 object_a и; ii) объект_b имеет 0 тиков, но object_a имеет > 0 тиков
Мой первый подход состоит в том, чтобы иметь две отдельные функции для обеих задач, читать CSV файлы (обычно размером 1,5 ГБ) в кусках и выводить извлеченные строки в другой CSV файл после группировки их в соответствии с родительским именем...
def objective_one(group_name, group_df):
group_df = group_df[group_df['Type'] == 'object_a']
if len(group_df) > 1:
zero_tick_object_a = group_df[group_df['Ticks'] == 0]
if len(zero_click_object_a) < len(group_df):
return zero_click_object_a
else:
return pd.DataFrame(columns=group_df.columns)
else:
return pd.DataFrame(columns=group_df.columns)
def objective_two(group_name, group_df):
object_a_in_group_df = group_df[group_df['Type'] == 'object_a']
object_b_has_no_clicks_in_group_df = group_df[(group_df['Type'] == 'object_b') & (group_df['Ticks'] == 0)]
if len(object_a_in_group_df) >= 1 and len(object_b_has_no_ticks_in_group_df) >= 1:
has_ticks_objects = objects_in_group_df[object_a_in_group_df['Ticks'] >= 1]
if len(has_ticks_object_a) > 0:
return object_B_has_no_ticks_in_group_df
else:
return pd.DataFrame(columns=group_df.columns)
else:
return pd.DataFrame(columns=group_df.columns)
Вот вызовы этих функций в основном методе:
for chunk in pd.read_csv(file, chunksize=500000):
#objective one
chunk_object_a = chunk.groupby(['Parent Name']).apply(lambda g: objective_one(g.name, g))
....
....
#objective two
chunk_object_b = chunk.groupby(['Parent Name']).apply(lambda g: objective_two(g.name, g))
# Затем запишите файлы данных, полученные методом применения, в файл csv
Проблема с этим подходом заключается в том, что, хотя он и дает мне вывод, который я хочу, он очень медленный в больших файлах в диапазоне 1 ГБ и выше. Другая проблема заключается в том, что чтение его в кусках из CSV может эффективно сократить некоторые группы пополам (то есть, имя родителя может быть разделено на один кусок, а следующий, что делает невозможным выделение неточного количества)
Есть ли способ оптимизировать это, чтобы сделать его быстрее, а также обойти мою проблему с блоком?
Ответы
Ответ 1
Мой выстрел по проблеме:
- извлечь все object_a под родительским именем, где i) есть > 1 object_a и; ii) объект_a имеет 0 тиков, но другой объект_a имеет > 0 тиков. то есть только с нулевыми тиками
- извлечь все object_b под родительским именем, где i) существует >= 1 object_a и; ii) объект_b имеет 0 тиков, но object_a имеет > 0 тики
Мое первое впечатление при чтении этого заключается в том, что фактический "Тип" не имеет особого значения, мы просто хотим иметь существующий object_a
s > 0 Ticks для каждой группы и извлекать все элементы с 0 тиками, независимо от их тип.
Учитывая, что мой подход был первым, чтобы создать новый столбец для подсчета количества тиков object_a
для любого родителя. Если это число > 0, это означает, что существует не менее 1 object_a
с Ticks > 0.
In [63]: df.groupby(['Parent Name']).apply(lambda x: x[x['Type'] == 'object_a']['Ticks'].sum())
Out[63]:
Parent Name
3217863 2
4556421 34
dtype: int64
Теперь объедините это в исходный DataFrame...
In [64]: sumATicks = df.groupby(['Parent Name']).apply(lambda x: x[x['Type'] == 'object_a']['Ticks'].sum())
In [65]: merged = df.merge(pd.DataFrame(sumATicks).rename(columns={0: 'nbATicks'}), left_on='Parent Name', right_index=True)
In [66]: merged
Out[66]:
Type Parent Name Ticks nbATicks
0 object_a 4556421 34 34
1 object_a 4556421 0 34
2 object_b 4556421 0 34
3 object_a 3217863 2 2
4 object_b 3217863 1 2
... и извлеките все интересные строки в соответствии с критериями, указанными выше:
In [67]: merged[(merged['nbATicks'] > 0) & (merged['Ticks'] == 0)]
Out[67]:
Type Parent Name Ticks nbATicks
1 object_a 4556421 0 34
2 object_b 4556421 0 34
Надеюсь, я не забуду ни одного случая...
Что касается проблемы с куском, почему бы вам просто не загрузить весь файл csv в память? Если это так, вы можете попробовать сортировать ParentName перед обработкой и разделить куски в соответствующих местах.
Ответ 2
Вот моя идея для проблемы:
Я думаю, что первая цель проще, потому что мы зависим только от строк с object_a. Мы можем использовать преобразование для преобразования условий в логический список:
df_1 = df.loc[df['Type']=='object_a']
object_a = df_1.loc[(df_1.groupby('Parent_Name')['Ticks'].transform(min)==0)&
(df_1.groupby('Parent_Name')['Ticks'].transform(max)>0)&
(a['Ticks']==0)
]
Out[1]:
Type Parent_Name Ticks
1 object_a 4556421 0
Для второй цели я создаю список Parent_Names, отвечающий требованиям для object_a. На следующем шаге isin используется для выбора только соответствующих строк.
a_condition = df.loc[df['Type']=='object_a'].groupby('Parent_Name').sum()
a_condition = a_condition[a_condition>0].index
object_b = df.loc[(df['Type']=='object_b')&
(df['Ticks']==0)&
(df['Parent_Name'].isin(a_condition))
]
Out[2]:
Type Parent_Name Ticks
2 object_b 4556421 0
Ответ 3
In [35]: df
Out[32]:
Type Parent Name Ticks
0 object_a 4556421 34
1 object_a 4556421 0
2 object_b 4556421 0
3 object_a 3217863 2
4 object_b 3217863 1
Совокупность данных в tuple
s
In [33]: df1 = df.groupby(['Parent Name',
'Type']).agg(lambda x: tuple(x)).unstack(1)
In [34]: df1
Out[34]:
Ticks
Type object_a object_b
Parent Name
3217863 (2,) (1,)
4556421 (34, 0) (0,)
Создайте булевскую маску для вашего случая # 1
In [35]: mask1 = df1.apply(lambda x: (len(x[0])>1) & ((x[0]).count(0)==1),
axis=1)
In [36]: mask1
Out[36]:
Parent Name
3217863 False
4556421 True
dtype: bool
Создайте булевскую маску для вашего случая # 2
In [37]: mask2 = df1.apply(lambda x: ((len(x[0])>=1) &
(len(set(x[0]).difference([0]))>0) &
(len(x[1])==1) &
(x[1][0]==0)),
axis=1)
In [38]: mask2
Out[38]:
Parent Name
3217863 False
4556421 True
dtype: bool
Получить результат для случая # 1
In [39]: df1.loc[mask1, [('Ticks', 'object_a')]]
Out[39]:
Ticks
Type object_a
Parent Name
4556421 (34, 0)
Получить результат для случая # 2
In [30]: df1.loc[mask2, [('Ticks', 'object_b')]]
Out[30]:
Ticks
Type object_b
Parent Name
4556421 (0,)