【问题标题】:Pandas: Filter dataframe for values that are too frequent or too rarePandas:过滤数据框以获取过于频繁或过于罕见的值
【发布时间】:2015-09-27 00:42:12
【问题描述】:

在 pandas 数据框上,我知道我可以对一个或多个列进行分组,然后过滤出现多于/少于给定数字的值。

但我想对数据框的每一列都执行此操作。我想删除太不频繁(假设发生的次数少于 5%)或太频繁的值。例如,考虑具有以下列的数据框:city of origin, city of destination, distance, type of transport (air/car/foot), time of day, price-interval

import pandas as pd
import string
import numpy as np
vals = [(c, np.random.choice(list(string.lowercase), 100, replace=True)) for c in 
    'city of origin', 'city of destination', 'distance, type of transport (air/car/foot)', 'time of day, price-interval']
df = pd.DataFrame(dict(vals))
>> df.head()
    city of destination     city of origin  distance, type of transport (air/car/foot)  time of day, price-interval
0   f   p   a   n
1   k   b   a   f
2   q   s   n   j
3   h   c   g   u
4   w   d   m   h

如果这是一个大数据框,则删除包含虚假项目的行是有意义的,例如,如果 time of day = night 仅出现 3% 的时间,或者如果 foot 传输方式很少,等等.

我想从所有列(或列列表)中删除所有此类值。我的一个想法是在每一列 transform 上做一个 value_counts 并为每个 value_counts 添加一列;然后根据它们是否高于或低于阈值进行过滤。但我认为一定有更好的方法来实现这一点?

【问题讨论】:

  • 看看sklearn特征检测

标签: python pandas filtering selection


【解决方案1】:

此过程将遍历 DataFrame 的每一列,并消除给定类别小于给定阈值百分比的行,从而在每个循环上缩小 DataFrame。

此答案与@Ami Tavory 提供的答案相似,但有一些细微差别:

  • 它将值计数标准化,因此您可以只使用百分位阈值。
  • 每列只计算一次计数,而不是两次。这样可以加快执行速度。

代码:

threshold = 0.03
for col in df:
    counts = df[col].value_counts(normalize=True)
    df = df.loc[df[col].isin(counts[counts > threshold].index), :]

代码时序:

df2 = pd.DataFrame(np.random.choice(list(string.lowercase), [1e6, 4], replace=True), 
                   columns=list('ABCD'))

%%timeit df=df2.copy()
threshold = 0.03
for col in df:
    counts = df[col].value_counts(normalize=True)
    df = df.loc[df[col].isin(counts[counts > threshold].index), :]

1 loops, best of 3: 485 ms per loop

%%timeit df=df2.copy()
m = 0.03 * len(df)
for c in df:
    df = df[df[c].isin(df[c].value_counts()[df[c].value_counts() > m].index)]

1 loops, best of 3: 688 ms per loop

【讨论】:

  • 您应该将for col in df 编辑为:for col in df.columns
  • 其他方式(您不需要指定.columns)。感谢您指出不一致之处。
  • 似乎归一化值计数返回计数的绝对 z 分数。有没有一种方法可以获得负归一化计数,这样我就可以过滤“太多”或“太少”的值而不是两者?
  • @emremrah normalize 返回相对频率,而不是 z 分数。这些相对频率是非负的,加起来是 100%。只需对这些标准化值进行排序即可得到“太少”/“太多”。
【解决方案2】:

我会选择以下之一:

选项 A

m = 0.03 * len(df)
df[np.all(
    df.apply(
        lambda c: c.isin(c.value_counts()[c.value_counts() > m].index).as_matrix()), 
    axis=1)]

解释:

  • m = 0.03 * len(df) 是阈值(从复杂的表达式中去掉常数很好)

  • df[np.all(..., axis=1)] 保留在所有列中获得某些条件的行。

  • df.apply(...).as_matrix 将一个函数应用于所有列,并生成一个结果矩阵。

  • c.isin(...) 检查每个列项是否在某个集合中。

  • c.value_counts()[c.value_counts() > m].index 是列中计数大于m 的所有值的集合。

选项 B

m = 0.03 * len(df)
for c in df.columns:
    df = df[df[c].isin(df[c].value_counts()[df[c].value_counts() > m].index)]

解释同上。


权衡:

  • 就我个人而言,我觉得 B 更具可读性。

  • B 为每列过滤创建一个新的DataFrame;对于大型 DataFrame,它可能更昂贵。

【讨论】:

  • 我在另一个 SO 线程 (stackoverflow.com/a/23202269/228177) 中发现了这个: df_f = df[(np.abs(stats.zscore(df))
  • 这是真的 - 它是相似的。 FWIW,我认为那里的答案是你应该使用的,而这里的答案是对你问题的严格回答。
  • 顺便说一句,只是为了创建一个 MWE,如何创建一系列随机字母或一组字母?例如,[A,A,A,B,Z,X,X,A]。您通过 np.random.randn(10) 创建数字系列,但字符串有类似的东西吗?
  • 您介意我将其编辑到问题中吗?这会更容易。
【解决方案3】:

我是 Python 新手并使用 Pandas。我想出了下面的解决方案。也许其他人可能有更好或更有效的方法。

假设您的 DataFrame 是 DF,您可以使用下面的代码过滤掉所有不常见的值。请务必更新 colbin_freq 变量。 DF_Filtered 是您的新过滤数据框。

# Column you want to filter
col = 'time of day'

# Set your frequency to filter out. Currently set to 5%  
bin_freq = float(5)/float(100)

DF_Filtered = pd.DataFrame()

for i in DF[col].unique():
    counts = DF[DF[col]==i].count()[col] 
    total_counts = DF[col].count()
    freq  = float(counts)/float(total_counts)

    if freq > bin_freq:
        DF_Filtered = pd.concat([DF[DF[col]==i],DF_Filtered])

print DF_Filtered

【讨论】:

    【解决方案4】:

    DataFrames 支持clip_lower(threshold, axis=None)clip_upper(threshold, axis=None),它们会删除低于或高于(分别)某个阈值的所有值。

    【讨论】:

    • 抱歉,这个截断,1.完全不同,2.适用于数值。
    【解决方案5】:

    我们还可以用一个标签替换所有稀有类别,例如“稀有”,如果这不能为预测增加价值,则稍后删除。

    # function finds the labels that are more than certain percentage/threshold
    def get_freq_labels(df, var, rare_perc):    
        df = df.copy()
        tmp = df.groupby(var)[var].count() / len(df)
        return tmp[tmp > rare_perc].index
    
    
    vars_cat = [val for val in data.columns if data[val].dtype=='O']
    for var in vars_cat:
        # find the frequent categories
        frequent_cat = get_freq_labels(data, var, 0.05)
        # replace rare categories by the string "Rare"
        data[var] = np.where(data[var].isin(
            frequent_cat ), data[var], 'Rare')
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-27
      • 2017-04-14
      • 1970-01-01
      • 2018-06-07
      • 1970-01-01
      • 1970-01-01
      • 2016-03-18
      • 1970-01-01
      相关资源
      最近更新 更多