Pandas系列(pandas.Series)中是否有类似查询方法的函数(pandas.Series.query())?

41

pandas.DataFrame.query() 方法在加载或绘图时,用于(预/后)筛选数据非常有用。它尤其适用于方法链。

我经常想要对一个 pandas.Series 应用相同的逻辑,例如在执行了像 df.value_counts 这样返回 pandas.Series 的方法之后。

示例

假设有一个巨大的表格,其中包含列 Player, Game, Points,我想绘制得分超过14次3分球的球员的直方图。我首先必须对每个球员的得分进行求和 (groupby -> agg),这将返回大约1000个球员及其总得分的系列。应用 .query 逻辑看起来会像这样:

df = pd.DataFrame({
    'Points': [random.choice([1,3]) for x in range(100)], 
    'Player': [random.choice(["A","B","C"]) for x in range(100)]})

(df
     .query("Points == 3")
     .Player.values_count()
     .query("> 14")
     .hist())

我发现的唯一解决方案是让我执行一个不必要的任务并且打破方法链:

(points_series = df
     .query("Points == 3")
     .groupby("Player").size()
points_series[points_series > 100].hist()

方法链接和查询方法可以帮助保持代码易读性,而子集过滤可能会很快变得混乱。

# just to make my point :)
series_bestplayers_under_100[series_prefiltered_under_100 > 0].shape

请帮我摆脱困境!谢谢


我不确定SO是否是这个问题的最佳提问地点,因为这是向该库的开发人员提问,所以在我的意见中最好在[github](https://github.com/pandas-dev/pandas/issues)上发布。 - EdChum
将问题标题更改为更符合 Stack Overflow 风格。 - dmeu
1
这里是 pandas GitHub 上讨论此事的问题 - Erik
3个回答

20

如果我理解正确,您可以添加query("Points > 100")

df = pd.DataFrame({'Points':[50,20,38,90,0, np.Inf],
                   'Player':['a','a','a','s','s','s']})

print (df)
  Player     Points
0      a  50.000000
1      a  20.000000
2      a  38.000000
3      s  90.000000
4      s   0.000000
5      s        inf

points_series = df.query("Points < inf").groupby("Player").agg({"Points": "sum"})['Points']
print (points_series)     
a = points_series[points_series > 100]
print (a)     
Player
a    108.0
Name: Points, dtype: float64


points_series = df.query("Points < inf")
                  .groupby("Player")
                  .agg({"Points": "sum"})
                  .query("Points > 100")

print (points_series)     
        Points
Player        
a        108.0

另一个解决方案是可调用选择

points_series = df.query("Points < inf")
                  .groupby("Player")
                  .agg({"Points": "sum"})['Points']
                  .loc[lambda x: x > 100]

print (points_series)     
Player
a    108.0
Name: Points, dtype: float64

经过修改的问题所得到的回答:

np.random.seed(1234)
df = pd.DataFrame({
    'Points': [np.random.choice([1,3]) for x in range(100)], 
    'Player': [np.random.choice(["A","B","C"]) for x in range(100)]})

print (df.query("Points == 3").Player.value_counts().loc[lambda x: x > 15])
C    19
B    16
Name: Player, dtype: int64

print (df.query("Points == 3").groupby("Player").size().loc[lambda x: x > 15])
Player
B    16
C    19
dtype: int64

1
哦,糟糕的例子!groupby 实际上返回一个 DataFrame。但是例如 pd.DataFrame.value_counts() 返回一个系列,你的解决方案非常有用!通过可调用函数进行选择,我之前不知道 - 谢谢。 - dmeu
好的,我为你的新问题添加答案 - callable 很少使用,因为它是新功能。 - jezrael
好的,我看到它最近才被添加。很好的补充! - dmeu

11

你可以使用pipe代替查询:

s.pipe(lambda x: x[x>0]).pipe(lambda x: x[x<10])

1
这只会给你布尔掩码,它没有查询或过滤系列数据。 - Michele Piccolini
1
@MichelePiccolini,你确定吗?它对我过滤了系列。 - iruvar
1
是的,它确实进行了过滤,但它不会返回布尔掩码。请注意,lambda表达式是x[x>0],而不仅仅是x>0 - Denziloe
这个 .pipe() 方法对我来说真是大开眼界。 - mgouin

10

为什么不将Series转换为DataFrame,进行查询,然后再转换回来呢?

df["Points"] = df["Points"].to_frame().query('Points > 100')["Points"]

在这里,.to_frame()将数据转换为DataFrame,并且末尾的["Points"]将其转换为Series。

方法.query()可以在Pandas对象具有1个或多个列的情况下一致地使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接