我有一个3级MultiIndex数据帧,我想对其进行切片,以便保留满足特定条件之前的所有值。举一个例子,我有以下数据框:
Col1 Col2
Date Range Label
'2018-08-01' 1 A 900 815
B 850 820
C 800 820
D 950 840
2 A 900 820
B 750 850
C 850 820
D 850 800
我想选择所有值,直到Col1小于Col2。一旦我有了Col1 我尝试了几种选择,但是还没有找到好的解决方案。我可以轻松保留Col1> Col2的所有数据: 但这不是我所需要的。我也一直在考虑循环遍历1级索引并使用pd.IndexSlice切片数据帧: ,然后连接各种数据帧。
但是,这效率不高(我的数据框具有超过300万个条目,因此我也必须考虑这一点),而且我遇到的问题是,对于不同的日期重复使用范围索引,因此我可能必须嵌套2个周期或类似的东西。 我确定必须有一个简单且更Python化的解决方案,但我找不到解决该问题的方法。 如果要生成上面的数据框进行测试,可以使用: 我尝试同时实现Adam.Er8和Alexandre B.的解决方案,它们与我为SO创建的测试数据框(而不是真实数据)一起正常工作。 对于更实际的测试用例,您可以使用以下示例: 或者,您可以从here下载hdf文件。这是我真正使用的数据框的一部分。 Col1 Col2
Date Range Label
'2018-08-01' 1 A 900 815
B 850 820
2 A 900 820
df_new=df[df['Col1']>df['Col2']]
idx = pd.IndexSlice
idx_lev1=df.index.get_level_values(1).unique()
for j in (idx_lev1):
df_lev1=df.loc[idx[:,j,:],:]
idxs=df_lev1.index.get_level_values(2)[np.where(df_lev1['Col1']<df_lev1['Col2'])[0][0]-1]
df_sliced= df_lev1.loc[idx[:,:,:idxs],:]
from io import StringIO
s="""
Date Range Label Col1 Col2
'2018-08-01' 1 A 900 815
'2018-08-01' 1 B 850 820
'2018-08-01' 1 C 800 820
'2018-08-01' 1 D 950 840
'2018-08-01' 2 A 900 820
'2018-08-01' 2 B 750 850
'2018-08-01' 2 C 850 820
'2018-08-01' 2 D 850 800
"""
df2 = pd.read_csv(StringIO(s),
sep='\s+',
index_col=['Date','Range','Label'])
更新:
问题是,在某些情况下,Col1值始终大于Col2,在这种情况下,我只想保留所有数据。到目前为止,提出的所有解决方案都无法真正解决这个问题。s="""
Date Range Label Col1 Col2
'2018-08-01' 1 1 900 815
'2018-08-01' 1 2 950 820
'2018-08-01' 1 3 900 820
'2018-08-01' 1 4 950 840
'2018-08-01' 2 1 900 820
'2018-08-01' 2 2 750 850
'2018-08-01' 2 3 850 820
'2018-08-01' 2 4 850 800
'2018-08-02' 1 1 900 815
'2018-08-02' 1 2 850 820
'2018-08-02' 1 3 800 820
'2018-08-02' 1 4 950 840
'2018-08-02' 2 1 900 820
'2018-08-02' 2 2 750 850
'2018-08-02' 2 3 850 820
'2018-08-02' 2 4 850 800
"""
答案 0 :(得分:3)
我尝试使用documentation对每一行进行编号,然后找到条件正确的第一行,并使用它仅过滤数字比该数字低的行。
尝试一下:
from collections import defaultdict
import pandas as pd
from io import StringIO
s="""
Date Range Label Col1 Col2
'2018-08-01' 1 1 900 815
'2018-08-01' 1 2 950 820
'2018-08-01' 1 3 900 820
'2018-08-01' 1 4 950 840
'2018-08-01' 2 1 900 820
'2018-08-01' 2 2 750 850
'2018-08-01' 2 3 850 820
'2018-08-01' 2 4 850 800
'2018-08-02' 1 1 900 815
'2018-08-02' 1 2 850 820
'2018-08-02' 1 3 800 820
'2018-08-02' 1 4 950 840
'2018-08-02' 2 1 900 820
'2018-08-02' 2 2 750 850
'2018-08-02' 2 3 850 820
'2018-08-02' 2 4 850 800
"""
df = pd.read_csv(StringIO(s),
sep='\s+',
index_col=['Date', 'Range', 'Label'])
groupby_date_range = df.groupby(["Date", "Range"])
df["cumcount"] = groupby_date_range.cumcount()
first_col1_lt_col2 = defaultdict(lambda: len(df), df[df['Col1'] < df['Col2']].groupby(["Date", "Range"])["cumcount"].min().to_dict())
result = df[df.apply(lambda row: row["cumcount"] < first_col1_lt_col2[row.name[:2]], axis=1)].drop(columns="cumcount")
print(result)
输出:
Col1 Col2
Date Range Label
'2018-08-01' 1 1 900 815
2 950 820
3 900 820
4 950 840
2 1 900 820
'2018-08-02' 1 1 900 815
2 850 820
2 1 900 820
答案 1 :(得分:1)
另一种方法是使用np.where
并选择第一个索引。
groupby中的as_index=False
使您有机会忽略groupby
中的索引列。看看这个discussion
代码:
df2 = df2.reset_index() \
.groupby(by=["Range", "Date"], as_index=False) \
.apply(lambda x: x.head(np.where(x.Col1 < x.Col2)[0][0])) \
.set_index(["Date", "Range", "Label"])
print(df2)
# Col1 Col2
# Date Range Label
# '2018-08-01' 1 A 900 815
# B 850 820
# 2 A 900 820
答案 2 :(得分:0)
首先,我们创建一个“ helper” 列以累加每个组。然后,我们筛选分组依据中Col1 < Col2
所在的所有行,并获得高于该行的累积量:
df2['cumcount'] = df2.groupby(level=1).cumcount()
dfs = []
for idx, d in df2.groupby(level=1):
n = d.loc[(d['Col1'] < d['Col2']), 'cumcount'].min()-1
dfs.append(d.loc[d['cumcount'].le(n)])
df_final = pd.concat(dfs).drop('cumcount', axis=1)
输出
Col1 Col2
Date Range Label
'2018-08-01' 1 A 900 815
B 850 820
2 A 900 820
答案 3 :(得分:0)
您可以执行以下操作:
# create a dataframe with a similar structure as yours
data={
'Date': ['2019-04-08', '2019-06-27', '2019-04-05', '2019-05-01', '2019-04-09', '2019-06-19', '2019-04-25', '2019-05-18', '2019-06-10', '2019-05-19', '2019-07-01', '2019-04-07', '2019-03-31', '2019-04-01', '2019-06-09', '2019-04-17', '2019-04-27', '2019-05-27', '2019-06-29', '2019-04-24'],
'Key1': ['B', 'B', 'C', 'A', 'C', 'B', 'A', 'C', 'A', 'C', 'A', 'A', 'C', 'A', 'A', 'B', 'B', 'B', 'A', 'A'],
'Col1': [670, 860, 658, 685, 628, 826, 871, 510, 707, 775, 707, 576, 800, 556, 833, 551, 591, 492, 647, 414],
'Col2': [442, 451, 383, 201, 424, 342, 315, 548, 321, 279, 379, 246, 269, 461, 461, 371, 342, 327, 226, 467],
}
df= pd.DataFrame(data)
df.sort_values(['Date', 'Key1'], ascending=True, inplace=True)
df.set_index(['Date', 'Key1'], inplace=True)
# here the real work starts
# temporarily create a dataframe with the comparison
# which has a simple numeric index to be used later
# to slice the original dataframe
df2= (df['Col1']<df['Col2']).reset_index()
# we only want to see the rows from the first row
# to the last row before a row in which Col1<Col2
all_unwanted= (df2.loc[df2[0] == True, [0]])
if len(all_unwanted) > 0:
# good there was such a row, so we can use it's index
# to slice our dataframe
show_up_to= all_unwanted.idxmin()[0]
else:
# no, there was no such row, so just display everything
show_up_to= len(df)
# use the row number to slice our dataframe
df.iloc[0:show_up_to]
输出为:
Col1 Col2
Date Key1
2019-03-31 C 800 269
2019-04-01 A 556 461
2019-04-05 C 658 383
2019-04-07 A 576 246
2019-04-08 B 670 442
2019-04-09 C 628 424
2019-04-17 B 551 371
--------------------------- <-- cutting off the following lines:
2019-04-24 A 414 467
2019-04-25 A 871 315
2019-04-27 B 591 342
2019-05-01 A 685 201
2019-05-18 C 510 548
2019-05-19 C 775 279
2019-05-27 B 492 327
2019-06-09 A 833 461
2019-06-10 A 707 321
2019-06-19 B 826 342
2019-06-27 B 860 451
2019-06-29 A 647 226
2019-07-01 A 707 379