【问题标题】:Pandas filter dataframe columns through substring matchPandas 通过子字符串匹配过滤数据框列
【发布时间】:2022-01-10 17:25:57
【问题描述】:

我有一个包含多列的数据框,例如:

     Name  Age   Fname
0    Alex   10   Alice
1     Bob   12     Bob
2  Clarke   13  clarke

我的过滤条件是检查Name 是否是对应Fname 的子字符串(不区分大小写)。

如果是平等,那么简单的事情就是:

df[df["Name"].str.lower() == df["Fname"].str.lower()]

有效。但是,我想要子字符串匹配,所以我认为in 会起作用,而不是==。但这会产生错误,因为它将参数之一解释为pd.Series。我的第一个问题是Why this difference in interpretation?

我尝试的另一种方法是使用.str.contains

df[df["Fname"].str.contains(df["Name"], case=False)]

它也将df["Name"] 解释为pd.Series,当然,也适用于参数中的一些 const 字符串。

eg. this works:
df[df["Fname"].str.contains("a", case=False)]

我想解决这种情况,因此我们非常感谢您提供这方面的任何帮助。

【问题讨论】:

    标签: python pandas string dataframe substring


    【解决方案1】:

    .str 访问器非常循环和缓慢。大多数时候最好使用列表理解。

    import pandas as pd
    import numpy as np
    import timeit
    import matplotlib.pyplot as plt
    import pandas.testing as pt
    
    def list_comprehension_lower(df):
        return df[[len(set(i)) == 1 for i in (zip([x.lower() for x in df['Name']],[y.lower() for y in df['Fname']]))]]
    
    def apply_axis_1_lower(df):
        return df[df.apply(lambda x: x['Name'].lower() in x['Fname'].lower(), axis=1)]
    
    def dot_string_lower(df):
        return df[df["Name"].str.lower() == df["Fname"].str.lower()]
    
    fig, ax = plt.subplots()
    res = pd.DataFrame(
        index=[1, 5, 10, 30, 50, 100, 300, 500, 700, 1000, 10000],
        columns='list_comprehension_lower apply_axis_1_lower dot_string_lower'.split(),
        dtype=float
    )
    
    for i in res.index:
        d = pd.concat([df]*i, ignore_index=True)
        for j in res.columns:
            stmt = '{}(d)'.format(j)
            setp = 'from __main__ import d, {}'.format(j)
            res.at[i, j] = timeit.timeit(stmt, setp, number=100)
    
    res.groupby(res.columns.str[4:-1], axis=1).plot(loglog=True, ax=ax);
    

    输出:

    现在,回到您原来的问题,您可以将 list_comprehension 与 zipin 一起使用:

    df.loc[2, 'Fname'] += ' Adams'
    
    df[[x in y for x, y in zip([x.lower() for x in df['Name']],[y.lower() for y in df['Fname']])]]
    

    输出:

         Name  Age         Fname
    1     Bob   12           Bob
    2  Clarke   13  clarke Adams
    

    【讨论】:

    • 这张图不是显示使用.str.lower()更好吗?还是我错过了什么?
    • 是的,在这种情况下,超出一定数量的行是正确的。str lower out 执行列表理解。
    • 列表理解是一个双 for 循环,所以图表很有意义,在某些时候,Pandas str 函数会胜过它
    【解决方案2】:

    你可以遍历索引轴:

    >>> df[df.apply(lambda x: x['Name'].lower() in x['Fname'].lower(), axis=1)]
    
         Name  Age   Fname
    1     Bob   12     Bob
    2  Clarke   13  clarke
    

    str.contains 在第一个参数 pat 中采用常量,而不是 Series

    【讨论】:

    • 在看到你的答案之前,我刚刚更新了我的答案。我们的答案现在是一样的! :D
    • 很遗憾,您在这里没有真正的选择。
    • 哇,那让我看看我能不能解决问题。
    • 对于 900K 行,耗时 8.13 秒。这还不算太糟糕。
    • 因为在这个最终过滤之前我做了很多处理,所以 8-10s 对我来说并不重要。因此接受这个答案。
    【解决方案3】:

    您可以使用.apply()axis=1 为每一行调用一个函数:

    subset = df[df.apply(lambda x: x['Name'].lower() in x['Fname'].lower(), axis=1)]
    

    输出:

    >>> subset
         Name  Age   Fname
    1     Bob   12     Bob
    2  Clarke   13  clarke
    

    【讨论】:

    • 我的错...我提到的那一行代码有一个错误(已编辑)。但请通读整个问题。这不是我想要的。我的要求是“子字符串匹配”
    • @vish4071 再次检查;我更新了答案:)
    • 是不是太贵了,尤其是大数据框?
    【解决方案4】:

    通过列表理解的其他选项是否适合您:

    df.loc[[left.lower() in right.lower() 
            for left, right 
            in zip(df.Name, df.Fname)]
           ]
    
         Name  Age   Fname
    1     Bob   12     Bob
    2  Clarke   13  clarke
    
    

    【讨论】:

      猜你喜欢
      • 2017-12-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-21
      • 2021-01-18
      • 2021-12-15
      相关资源
      最近更新 更多