Улучшение производительности Python для циклов с фреймами данных Pandas - PullRequest
0 голосов
/ 01 июля 2018

пожалуйста, обратите внимание на следующую DataFrame df:

timestamp    id        condition
             1234      A    
             2323      B
             3843      B
             1234      C
             8574      A
             9483      A

Исходя из условия, содержащегося в условии столбца, я должен определить новый столбец в этом фрейме данных, который подсчитывает, сколько идентификаторов находятся в этом состоянии. Тем не менее, обратите внимание, что, поскольку DataFrame упорядочен по столбцу timestamp, можно иметь несколько записей с одним и тем же идентификатором, и тогда простой .cumsum () не является приемлемым вариантом.

Я получил следующий код, который работает правильно, но очень медленно:

#I start defining empty arrays
ids_with_condition_a = np.empty(0)
ids_with_condition_b = np.empty(0)
ids_with_condition_c = np.empty(0)

#Initializing new column
df['count'] = 0

#Using a for loop to do the task, but this is sooo slow!
for r in range(0, df.shape[0]):
    if df.condition[r] == 'A':
        ids_with_condition_a = np.append(ids_with_condition_a, df.id[r])
    elif df.condition[r] == 'B':
        ids_with_condition_b = np.append(ids_with_condition_b, df.id[r])
        ids_with_condition_a = np.setdiff1d(ids_with_condition_a, ids_with_condition_b)
    elifif df.condition[r] == 'C':
        ids_with_condition_c = np.append(ids_with_condition_c, df.id[r])

df.count[r] = ids_with_condition_a.size

Хранение этих массивов Numpy очень полезно для меня, потому что оно дает список идентификаторов в определенном состоянии. Я также смог бы динамически поместить эти массивы в соответствующую ячейку в df DataFrame.

Можете ли вы предложить лучшее решение с точки зрения производительности?

1 Ответ

0 голосов
/ 01 июля 2018

вам нужно использовать groupby для столбца 'условие' и cumcount, чтобы подсчитать, сколько идентификаторов в каждом условии до текущей строки (что, по-видимому, и делает ваш код):

df['count'] = df.groupby('condition').cumcount()+1 # +1 is to start at 1 not 0

с вашим входным образцом, вы получите:

     id condition  count
0  1234         A      1
1  2323         B      1
2  3843         B      2
3  1234         C      1
4  8574         A      2
5  9483         A      3

, что быстрее, чем при использовании цикла for

и, если вы хотите, например, иметь строку с условием A, вы можете использовать маску, например, если вы делаете print (df[df['condition'] == 'A']), вы видите строку с условием egal для A. Итак, чтобы получить массив,

arr_A = df.loc[df['condition'] == 'A','id'].values
print (arr_A)
array([1234, 8574, 9483])

РЕДАКТИРОВАТЬ: чтобы создать два столбца для условий, вы можете сделать, например, для условия A:

# put 1 in a column where the condition is met
df['nb_cond_A'] = pd.np.where(df['condition'] == 'A',1,None)
# then use cumsum for increment number, ffill to fill the same number down
# where the condition is not meet, fillna(0) for filling other missing values
df['nb_cond_A'] = df['nb_cond_A'].cumsum().ffill().fillna(0).astype(int)
# for the partial list, first create the full array
arr_A = df.loc[df['condition'] == 'A','id'].values
# create the column with apply (here another might exist, but it's one way)
df['partial_arr_A'] = df['nb_cond_A'].apply(lambda x: arr_A[:x])

вывод выглядит так:

     id condition  nb_condition_A       partial_arr_A  nb_cond_A
0  1234         A               1              [1234]          1
1  2323         B               1              [1234]          1
2  3843         B               1              [1234]          1
3  1234         C               1              [1234]          1
4  8574         A               2        [1234, 8574]          2
5  9483         A               3  [1234, 8574, 9483]          3

то же самое для B, C. Может быть с циклом for cond in set(df['condition']) может быть полезно для обобщения

РЕДАКТИРОВАТЬ 2: одна идея сделать то, что вы пояснили в комментариях, но не уверены, что это улучшает производительность:

# array of unique condition
arr_cond = df.condition.unique()
#use apply to create row-wise the list of ids for each condition
df[arr_cond] = (df.apply(lambda row: (df.loc[:row.name].drop_duplicates('id','last')
                                          .groupby('condition').id.apply(list)) ,axis=1)
                  .applymap(lambda x: [] if not isinstance(x,list) else x))

Некоторые пояснения: для каждой строки выберите кадр данных до этой строки loc[:row.name], отбросьте дублированный «id» и оставьте последний drop_duplicates('id','last') (в вашем примере это означает, что как только мы достигнем строки 3, строка 0 удаляется, так как идентификатор 1234 удваивается), затем данные группируются по условию groupby('condition'), и идентификаторы для каждого условия помещаются в один и тот же список id.apply(list). Часть, начинающаяся с applymap fillna с пустым списком (вы не можете использовать fillna ([]), это невозможно).

Для длины для каждого условия вы можете сделать:

for cond in arr_cond:
    df['len_{}'.format(cond)] = df[cond].str.len().fillna(0).astype(int)

Результат такой:

     id condition             A             B       C  len_A  len_B  len_C
0  1234         A        [1234]            []      []      1      0      0
1  2323         B        [1234]        [2323]      []      1      1      0
2  3843         B        [1234]  [2323, 3843]      []      1      2      0
3  1234         C            []  [2323, 3843]  [1234]      0      2      1
4  8574         A        [8574]  [2323, 3843]  [1234]      1      2      1
5  9483         A  [8574, 9483]  [2323, 3843]  [1234]      2      2      1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...