Более быстрый способ выполнить эту задачу 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,)