如何在pandas中有条件地切片数据帧

时间:2018-04-26 18:33:10

标签: python pandas dataframe

考虑一个pandas DataFrame构造如下:

df = pandas.DataFrame({'a':['one','two','three']})

然后我可以找到包含two的数据框的特定行,如:

df[df.a == 'two']

但到目前为止,我发现将DataFrame子集到此行的唯一方法是:

df[:df[df.a == 'two'].index[0]]

但这很难看,所以:

是否有更合适的方法来完成此子集化?

具体来说,我感兴趣的是如何在行索引之间切片DataFrame,其中给定列匹配某些任意文本字符串(在本例中为' two')。对于这种特殊情况,它将等同于df[:2]。但是,一般情况下,基于列值定位切片开始和/或结束的索引的能力似乎是合理的吗?

最后一个例子,也许会有所帮助;我希望能够做到这样的事情:

df[df.a == 'one' : df.a == 'three']

获取包含行1和1的切片2个DataFrame,相当于df [0:3]

2 个答案:

答案 0 :(得分:1)

您希望识别特定开始和停止值的索引,并获取匹配的行以及其间的所有行。一种方法是找到索引并建立一个范围,但你已经说过你不喜欢这种方法。这是一个使用布尔逻辑的通用解决方案,应该适合你。

首先,让我们做一个更有趣的例子:

import pandas as pd
df = pd.DataFrame({'a':['one','two','three', 'four', 'five']})

假设start = "two"stop = "four"。也就是说,您希望获得以下输出DataFrame:

       a
1    two
2  three
3   four

我们可以通过以下方式找到边界行的索引:

df["a"].isin({start, stop})
#0    False
#1     True
#2    False
#3     True
#4    False
#Name: a, dtype: bool

如果索引2的值是True,我们就可以完成,因为我们可以将此输出用作掩码。因此,让我们找到一种方法来创建我们需要的面具。

首先我们可以使用cummax()和布尔XOR运算符(^)来实现:

(df["a"]==start).cummax() ^ (df["a"]==stop).cummax()
#0    False
#1     True
#2     True
#3    False
#4    False
#Name: a, dtype: bool

这几乎是我们想要的,除了我们缺少停止值索引。所以,让我们按位OR(|)停止条件:

#0    False
#1     True
#2     True
#3     True
#4    False
#Name: a, dtype: bool

这会得到我们正在寻找的结果。因此,创建一个掩码,并索引数据帧:

mask = (df["a"]==start).cummax() ^ (df["a"]==stop).cummax() | (df["a"]==stop)
print(df[mask])
#       a
#1    two
#2  three
#3   four

我们可以将这些发现扩展到一个函数,该函数还支持从一行到最后一行索引或索引:

def get_rows(df, col, start, stop):
    if start is None:
        mask = ~((df[col] == stop).cummax() ^ (df[col] == stop))
    else:
        mask = (df[col]==start).cummax() ^ (df[col]==stop).cummax() | (df[col]==stop)
    return df[mask]

# get rows between "two" and "four" inclusive
print(get_rows(df=df, col="a", start="two", stop="four"))
#       a
#1    two
#2  three
#3   four

# get rows from "two" until the end
print(get_rows(df=df, col="a", start="two", stop=None))
#       a
#1    two
#2  three
#3   four
#4   five

# get rows up to "two"
print(get_rows(df=df, col="a", start=None, stop="two"))
#     a
#0  one
#1  two

更新

为了完整性,这里是基于索引的解决方案。

def get_rows_indexing(df, col, start, stop):
    min_ind = min(df.index[df[col]==start].tolist() or [0])
    max_ind = max(df.index[df[col]==stop].tolist() or [len(df)])
    return df[min_ind:max_ind+1]

此功能与其他版本的功能基本相同,但可能更容易理解。此外,这更加强大,因为另一个版本依赖于None不是所需列中的值。

答案 1 :(得分:1)

如果您暂时将“ a”列用作索引,那么定位方法(loc)完全可以满足您的要求。

df = pd.DataFrame({'a':['one','two','three', 'four', 'five']})
start = 'two'
stop = 'four'
df = df.set_index('a').loc[start:stop].reset_index()