我正在尝试从 groupby 中的第一个非顺序“期间”开始删除数据框中的任何行。如果可能,我宁愿避免循环。
import pandas as pd
data = {'Country': ['DE', 'DE', 'DE', 'DE', 'DE', 'US', 'US', 'US', 'US','US'],
'Product': ['Blue', 'Blue', 'Blue', 'Blue','Blue','Green', 'Green', 'Green', 'Green','Green'],
'Period': [1, 2, 3,5,6, 1, 2, 4, 5, 6]}
df = pd.DataFrame(data, columns= ['Country','Product', 'Period'])
print df
Run Code Online (Sandbox Code Playgroud)
输出:
Country Product Period
0 DE Blue 1
1 DE Blue 2
2 DE Blue 3
3 DE Blue 5
4 DE Blue 6
5 US Green 1
6 US Green 2
7 US Green 4
8 US Green 5
9 US Green 6
Run Code Online (Sandbox Code Playgroud)
例如,我想要的最终输出如下:
Country Product Period
0 DE Blue 1
1 DE Blue 2
2 DE Blue 3
5 US Green 1
6 US Green 2
Run Code Online (Sandbox Code Playgroud)
我试图这样做的方式是下面给你一个想法,但我有很多错误。但是你可能会看到我试图做的事情的逻辑。
df = df.groupby(['Country','Product']).apply(lambda x: x[x.Period.shift(x.Period - 1) == 1]).reset_index(drop=True)
Run Code Online (Sandbox Code Playgroud)
棘手的部分不仅仅是使用 .shift(1) 或我试图将一个值输入到 .shift() 的东西,即如果该行 Period 是 5 那么我想说 .shift(5-1) 所以它转移向上 4 个位置并检查该周期的值。如果它等于 1,则表示它仍然是连续的。在这种情况下,我猜它会进入楠领土。
shift()您可以使用diff()and代替使用cumsum():
result = grouped['Period'].apply(
lambda x: x.loc[(x.diff() > 1).cumsum() == 0])
Run Code Online (Sandbox Code Playgroud)
import pandas as pd
data = {'Country': ['DE', 'DE', 'DE', 'DE', 'DE', 'US', 'US', 'US', 'US','US'],
'Product': ['Blue', 'Blue', 'Blue', 'Blue','Blue','Green', 'Green', 'Green', 'Green','Green'],
'Period': [1, 2, 3,5,6, 1, 2, 4, 5, 6]}
df = pd.DataFrame(data, columns= ['Country','Product', 'Period'])
print(df)
grouped = df.groupby(['Country','Product'])
result = grouped['Period'].apply(
lambda x: x.loc[(x.diff() > 1).cumsum() == 0])
result.name = 'Period'
result = result.reset_index(['Country', 'Product'])
print(result)
Run Code Online (Sandbox Code Playgroud)
产量
Country Product Period
0 DE Blue 1
1 DE Blue 2
2 DE Blue 3
5 US Green 1
6 US Green 2
Run Code Online (Sandbox Code Playgroud)
说明:
连续运行的数字的相邻差异为 1。例如,如果我们暂时将其df['Period']视为所有一组的一部分,
In [41]: df['Period'].diff()
Out[41]:
0 NaN
1 1
2 1
3 2
4 1
5 -5
6 1
7 2
8 1
9 1
Name: Period, dtype: float64
In [42]: df['Period'].diff() > 1
Out[42]:
0 False
1 False
2 False
3 True <--- We want to cut off before here
4 False
5 False
6 False
7 True
8 False
9 False
Name: Period, dtype: bool
Run Code Online (Sandbox Code Playgroud)
为了找到截断的位置-第一个True在df['Period'].diff() > 1-我们可以使用cumsum(),并选择那些等于0行:
In [43]: (df['Period'].diff() > 1).cumsum()
Out[43]:
0 0
1 0
2 0
3 1
4 1
5 1
6 1
7 2
8 2
9 2
Name: Period, dtype: int64
In [44]: (df['Period'].diff() > 1).cumsum() == 0
Out[44]:
0 True
1 True
2 True
3 False
4 False
5 False
6 False
7 False
8 False
9 False
Name: Period, dtype: bool
Run Code Online (Sandbox Code Playgroud)
取diff()andcumsum()可能看起来很浪费,因为这些操作可能会计算很多不需要的值——特别是如果x非常大并且第一次连续运行非常短。
尽管存在浪费,但通过调用 NumPy 或 Pandas 方法(在 C/Cython/C++ 或 Fortran 中实现)获得的速度通常胜过用纯 Python 编码的不那么浪费的算法。
但是,您可以取代呼叫cumsum通过调用argmax:
result = grouped['Period'].apply(
lambda x: x.loc[:(x.diff() > 1).argmax()].iloc[:-1])
Run Code Online (Sandbox Code Playgroud)
对于非常大的,x这可能会更快一些:
x = df['Period']
x = pd.concat([x]*1000)
x = x.reset_index(drop=True)
In [68]: %timeit x.loc[:(x.diff() > 1).argmax()].iloc[:-1]
1000 loops, best of 3: 884 µs per loop
In [69]: %timeit x.loc[(x.diff() > 1).cumsum() == 0]
1000 loops, best of 3: 1.12 ms per loop
Run Code Online (Sandbox Code Playgroud)
但是请注意,它argmax返回的是索引级别值,而不是有序索引位置。因此,如果x.index包含重复值,则使用 argmax 将不起作用。(这就是我必须设置的原因x = x.reset_index(drop=True)。)
因此,虽然argmax在某些情况下使用会更快一些,但这种替代方案并不那么健壮。