将 pandas 数据框中的行和上一行与数百万行进行比较的最快方法
Fastest way to compare row and previous row in pandas dataframe with millions of rows
我正在寻找解决方案来加速我编写的用于遍历 pandas 数据帧并比较当前行和上一行之间的列值的函数。
例如,这是我的问题的简化版本:
User Time Col1 newcol1 newcol2 newcol3 newcol4
0 1 6 [cat, dog, goat] 0 0 0 0
1 1 6 [cat, sheep] 0 0 0 0
2 1 12 [sheep, goat] 0 0 0 0
3 2 3 [cat, lion] 0 0 0 0
4 2 5 [fish, goat, lemur] 0 0 0 0
5 3 9 [cat, dog] 0 0 0 0
6 4 4 [dog, goat] 0 0 0 0
7 4 11 [cat] 0 0 0 0
目前,我有一个函数可以循环计算“newcol1
”和“newcol2
”的值,这取决于“User
”自上次以来是否发生了变化行以及“Time
”值的差异是否大于 1。它还会查看存储在“Col1
”和“Col2
”中的数组中的第一个值并更新'newcol3
' 和 'newcol4
' 如果这些值自上一行以来发生了变化。
这是我目前正在做的伪代码(因为我已经简化了问题,所以我还没有测试过这个,但它与我在 ipython notebook 中实际做的非常相似) :
def myJFunc(df):
... #initialize jnum counter
... jnum = 0;
... #loop through each row of dataframe (not including the first/zeroeth)
... for i in range(1,len(df)):
... #has user changed?
... if df.User.loc[i] == df.User.loc[i-1]:
... #has time increased by more than 1 (hour)?
... if abs(df.Time.loc[i]-df.Time.loc[i-1])>1:
... #update new columns
... df['newcol2'].loc[i-1] = 1;
... df['newcol1'].loc[i] = 1;
... #increase jnum
... jnum += 1;
... #has content changed?
... if df.Col1.loc[i][0] != df.Col1.loc[i-1][0]:
... #record this change
... df['newcol4'].loc[i-1] = [df.Col1.loc[i-1][0], df.Col2.loc[i][0]];
... #different user?
... elif df.User.loc[i] != df.User.loc[i-1]:
... #update new columns
... df['newcol1'].loc[i] = 1;
... df['newcol2'].loc[i-1] = 1;
... #store jnum elsewhere (code not included here) and reset jnum
... jnum = 1;
我现在需要将这个函数应用到几百万行,它的速度慢得不可思议,所以我想找出加速它的最佳方法。我听说 Cython 可以提高函数的速度,但我没有使用它的经验(而且我对 pandas 和 python 都是新手)。是否可以将数据帧的两行作为参数传递给函数,然后使用 Cython 来加速它,或者是否有必要创建其中包含“diff
”值的新列,以便函数仅从中读取并一次写入一行数据帧,以便从使用 Cython 中获益?任何其他速度技巧将不胜感激!
(关于使用 .loc,我比较了 .loc、.iloc 和 .ix,这个稍微快一点,所以这是我目前使用它的唯一原因)
(此外,我的 User
列实际上是 unicode 而不是 int,这对于快速比较可能会有问题)
在您的问题中,您似乎想要成对地遍历行。你可以做的第一件事是这样的:
from itertools import tee, izip
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return izip(a, b)
for (idx1, row1), (idx2, row2) in pairwise(df.iterrows()):
# you stuff
但是您不能直接修改 row1 和 row2,您仍然需要对索引使用 .loc 或 .iloc。
如果 iterrows 仍然太慢,我建议这样做:
使用 pd.unique(用户)从你的 unicode 名称创建一个 user_id 列,并将名称与字典映射到整数 ID。
创建一个增量数据帧:将原始数据帧减去 user_id 和时间列的移位数据帧。
df[[col1, ..]].shift() - df[[col1, ..]])
如果user_id > 0,表示用户在连续两行发生变化。时间列可以直接用delta[delta['time' > 1]]过滤
使用此增量数据框,您可以按行记录更改。您可以使用它作为掩码来更新原始数据框中所需的列。
使用 pandas(构造)并矢量化您的代码,即不要使用 for 循环,而是使用 pandas/numpy 函数。
'newcol1' and 'newcol2' based on whether the 'User' has changed since the previous row and also whether the difference in the 'Time' values is greater than 1.
分别计算这些:
df['newcol1'] = df['User'].shift() == df['User']
df.ix[0, 'newcol1'] = True # possibly tweak the first row??
df['newcol1'] = (df['Time'].shift() - df['Time']).abs() > 1
我不清楚 Col1 的用途,但列中的一般 python 对象不能很好地缩放(您不能使用快速路径,内容分散在内存中)。大多数时候你可以使用其他东西来逃避......
Cython 是最后一个选项,在 99% 的用例中不需要,但请参阅 enhancing performance section of the docs 获取提示。
我的想法与安迪相同,只是添加了 groupby
,我认为这是对安迪回答的补充。每当您执行 diff
或 shift
时,添加 groupby 只会产生将 NaN 放在第一行的效果。 (请注意,这并不是试图给出确切的答案,只是勾勒出一些基本的技术。)
df['time_diff'] = df.groupby('User')['Time'].diff()
df['Col1_0'] = df['Col1'].apply( lambda x: x[0] )
df['Col1_0_prev'] = df.groupby('User')['Col1_0'].shift()
User Time Col1 time_diff Col1_0 Col1_0_prev
0 1 6 [cat, dog, goat] NaN cat NaN
1 1 6 [cat, sheep] 0 cat cat
2 1 12 [sheep, goat] 6 sheep cat
3 2 3 [cat, lion] NaN cat NaN
4 2 5 [fish, goat, lemur] 2 fish cat
5 3 9 [cat, dog] NaN cat NaN
6 4 4 [dog, goat] NaN dog NaN
7 4 11 [cat] 7 cat dog
作为 Andy 关于存储对象的观点的后续,请注意我在这里所做的是提取列表列的第一个元素(并且还添加了一个移位版本)。这样做你只需要做一次昂贵的提取,然后就可以坚持标准 pandas 方法。
我正在寻找解决方案来加速我编写的用于遍历 pandas 数据帧并比较当前行和上一行之间的列值的函数。
例如,这是我的问题的简化版本:
User Time Col1 newcol1 newcol2 newcol3 newcol4
0 1 6 [cat, dog, goat] 0 0 0 0
1 1 6 [cat, sheep] 0 0 0 0
2 1 12 [sheep, goat] 0 0 0 0
3 2 3 [cat, lion] 0 0 0 0
4 2 5 [fish, goat, lemur] 0 0 0 0
5 3 9 [cat, dog] 0 0 0 0
6 4 4 [dog, goat] 0 0 0 0
7 4 11 [cat] 0 0 0 0
目前,我有一个函数可以循环计算“newcol1
”和“newcol2
”的值,这取决于“User
”自上次以来是否发生了变化行以及“Time
”值的差异是否大于 1。它还会查看存储在“Col1
”和“Col2
”中的数组中的第一个值并更新'newcol3
' 和 'newcol4
' 如果这些值自上一行以来发生了变化。
这是我目前正在做的伪代码(因为我已经简化了问题,所以我还没有测试过这个,但它与我在 ipython notebook 中实际做的非常相似) :
def myJFunc(df):
... #initialize jnum counter
... jnum = 0;
... #loop through each row of dataframe (not including the first/zeroeth)
... for i in range(1,len(df)):
... #has user changed?
... if df.User.loc[i] == df.User.loc[i-1]:
... #has time increased by more than 1 (hour)?
... if abs(df.Time.loc[i]-df.Time.loc[i-1])>1:
... #update new columns
... df['newcol2'].loc[i-1] = 1;
... df['newcol1'].loc[i] = 1;
... #increase jnum
... jnum += 1;
... #has content changed?
... if df.Col1.loc[i][0] != df.Col1.loc[i-1][0]:
... #record this change
... df['newcol4'].loc[i-1] = [df.Col1.loc[i-1][0], df.Col2.loc[i][0]];
... #different user?
... elif df.User.loc[i] != df.User.loc[i-1]:
... #update new columns
... df['newcol1'].loc[i] = 1;
... df['newcol2'].loc[i-1] = 1;
... #store jnum elsewhere (code not included here) and reset jnum
... jnum = 1;
我现在需要将这个函数应用到几百万行,它的速度慢得不可思议,所以我想找出加速它的最佳方法。我听说 Cython 可以提高函数的速度,但我没有使用它的经验(而且我对 pandas 和 python 都是新手)。是否可以将数据帧的两行作为参数传递给函数,然后使用 Cython 来加速它,或者是否有必要创建其中包含“diff
”值的新列,以便函数仅从中读取并一次写入一行数据帧,以便从使用 Cython 中获益?任何其他速度技巧将不胜感激!
(关于使用 .loc,我比较了 .loc、.iloc 和 .ix,这个稍微快一点,所以这是我目前使用它的唯一原因)
(此外,我的 User
列实际上是 unicode 而不是 int,这对于快速比较可能会有问题)
在您的问题中,您似乎想要成对地遍历行。你可以做的第一件事是这样的:
from itertools import tee, izip
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return izip(a, b)
for (idx1, row1), (idx2, row2) in pairwise(df.iterrows()):
# you stuff
但是您不能直接修改 row1 和 row2,您仍然需要对索引使用 .loc 或 .iloc。
如果 iterrows 仍然太慢,我建议这样做:
使用 pd.unique(用户)从你的 unicode 名称创建一个 user_id 列,并将名称与字典映射到整数 ID。
创建一个增量数据帧:将原始数据帧减去 user_id 和时间列的移位数据帧。
df[[col1, ..]].shift() - df[[col1, ..]])
如果user_id > 0,表示用户在连续两行发生变化。时间列可以直接用delta[delta['time' > 1]]过滤 使用此增量数据框,您可以按行记录更改。您可以使用它作为掩码来更新原始数据框中所需的列。
使用 pandas(构造)并矢量化您的代码,即不要使用 for 循环,而是使用 pandas/numpy 函数。
'newcol1' and 'newcol2' based on whether the 'User' has changed since the previous row and also whether the difference in the 'Time' values is greater than 1.
分别计算这些:
df['newcol1'] = df['User'].shift() == df['User']
df.ix[0, 'newcol1'] = True # possibly tweak the first row??
df['newcol1'] = (df['Time'].shift() - df['Time']).abs() > 1
我不清楚 Col1 的用途,但列中的一般 python 对象不能很好地缩放(您不能使用快速路径,内容分散在内存中)。大多数时候你可以使用其他东西来逃避......
Cython 是最后一个选项,在 99% 的用例中不需要,但请参阅 enhancing performance section of the docs 获取提示。
我的想法与安迪相同,只是添加了 groupby
,我认为这是对安迪回答的补充。每当您执行 diff
或 shift
时,添加 groupby 只会产生将 NaN 放在第一行的效果。 (请注意,这并不是试图给出确切的答案,只是勾勒出一些基本的技术。)
df['time_diff'] = df.groupby('User')['Time'].diff()
df['Col1_0'] = df['Col1'].apply( lambda x: x[0] )
df['Col1_0_prev'] = df.groupby('User')['Col1_0'].shift()
User Time Col1 time_diff Col1_0 Col1_0_prev
0 1 6 [cat, dog, goat] NaN cat NaN
1 1 6 [cat, sheep] 0 cat cat
2 1 12 [sheep, goat] 6 sheep cat
3 2 3 [cat, lion] NaN cat NaN
4 2 5 [fish, goat, lemur] 2 fish cat
5 3 9 [cat, dog] NaN cat NaN
6 4 4 [dog, goat] NaN dog NaN
7 4 11 [cat] 7 cat dog
作为 Andy 关于存储对象的观点的后续,请注意我在这里所做的是提取列表列的第一个元素(并且还添加了一个移位版本)。这样做你只需要做一次昂贵的提取,然后就可以坚持标准 pandas 方法。