请考虑以下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中的相应单元格中。
在性能方面,您能够提出更好的解决方案吗?
答案 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