使用Pandas数据框提高Python循环性能

时间:2018-07-01 10:27:51

标签: python performance pandas

请考虑以下DataFrame df:

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

基于列条件中包含的条件,我必须在此数据框中定义一个新列,该列计算该条件中有多少个id。 但是,请注意,由于DataFrame是由timestamp列排序的,因此可能会有多个具有相同id的条目,然后简单的.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数组对我非常有用,因为它会给出特定条件下的ID列表。我也可以将这些数组动态地放入df DataFrame中的相应单元格中。

在性能方面,您能够提出更好的解决方案吗?

1 个答案:

答案 0 :(得分:1)

您需要在“条件”列上使用groupby,并在cumcount上计算直到当前行为止每个条件中有多少个ID(这似乎是您的代码所要做的): / p>

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']),您看到的行仅包含条件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行,因为id 1234是两次),然后按条件groupby('condition')将数据分组,并将每个条件的id放在同一列表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