有条件地对很多特征进行二值化

Binarize a lot of features with condition

我有 Pandas 数据框,其中包含数百个分类特征(以数字表示)。我只想在列中保留最高值。我已经知道,每列中只有 3 或 4 个最常见的值,但我想自动 select 它。我需要两种方法:

1) 只保留 3 个最常见的值。 概念:没有列具有 1、2 或 3 个唯一值(每列约 20 个唯一值),因此,不要考虑它。例如,如果您有多个第三名,请将它们全部保留。例如:

#after you use value_counts() column 1
1         35
2         23
3         10
4         9
8         8
6         8

#after you use value_counts() on column 2
0         23
2         15
1         15 #two second places
4         9
5         3
6         2

#result after you use value_counts() on column 1
1         35
2         23
3         10
others  25 #9+8+8

#result after you use value_counts() on column 2
0         23
2         15
1         15
4         9
others 5 #3+2

2) 根据需要在每列中保留尽可能多的值,以便剩余值的数量少于您决定保留的最后一个值的数量。例如:

#after you use value_counts() column 1
1         35
2         23
3         10
4         3
8         2
6         1

#after you use value_counts() on column 2
0         23
2         15
1         9
4         8
5         3
6         2

#result after you use value_counts() on column 1
1         35
2         23
3         10
others  6 #3+2+1

#result after you use value_counts() on column 2
0         23
2         15
1         9
4         8
others 5 #3+2

请同时进行。谢谢。

让我们用您的逻辑试试 udf:

def my_count(s):
    x = s.value_counts()
    if len(x) > 3:
        ret = x.iloc[:3].copy()
        ret.loc['other'] = x.iloc[3:].sum()
    else:
        ret = x
    return ret

df[['col1']].apply(my_count)

输出:

       col1
1        35
2        23
3        10
other     6

我将展示我想在工作中使用 2 列数据。 局限性:第2、3、4位同时出现的并列在该解中没有收集到同一个单元格中。您可能需要根据您的目的进一步自定义此行为。

示例数据

有 2 列,每列 26 类。一列是分类的,另一列是数字的。特意选择示例数据以展示关系的影响。

import pandas as pd
import numpy as np

np.random.seed(2)  # reproducibility
df = pd.DataFrame(np.random.randint(65, 91, (1000, 2)), columns=["str", "num"])
df["str"] = list(map(chr, df["str"].values))

print(df)
    str  num
0     I   80
1     N   73
2     W   76
3     S   76
4     I   72
..   ..  ...
995   M   80
996   Q   70
997   P   66
998   I   87
999   F   83
[1000 rows x 2 columns]

所需函数

def count_top_n(df, n_top):

    # name of output columns
    def gen_cols(ls_str):
        for s in ls_str:
            yield s
            yield f"{s}_counts"

    df_count = pd.DataFrame(np.zeros((n_top+1, df.shape[1]*2), dtype=object),
                            index=range(1, n_top+2),
                            columns=list(gen_cols(df.columns.values)))  # df.shape[1] = #cols
    # process each column
    for i, col in enumerate(df):
        # count
        tmp = df[col].value_counts()
        assert len(tmp) > n_top, f"ValueError: too few classes {len(tmp)} <= {n_top} = n_top)"

        # case 1: no ties at the 3rd place
        if tmp.iat[n_top - 1] != tmp.iat[n_top]:
            # fill in classes
            df_count.iloc[:n_top, 2*i] = tmp[:n_top].index.values
            df_count.iloc[n_top, 2*i] = "(rest)"
            # fill counts
            df_count.iloc[:n_top, 2*i+1] = tmp[:n_top]
            df_count.iloc[n_top, 2*i+1] = tmp[n_top:].sum()
        
        # case 2: ties
        else:
            # new termination location
            n_top_new = (tmp >= tmp.iat[n_top]).sum()
            # fill in classes
            df_count.iloc[:n_top-1, 2*i] = tmp.iloc[:n_top-1].index.values
            df_count.iloc[n_top-1, 2*i] = list(tmp.iloc[n_top-1:n_top_new].index.values)
            df_count.iloc[n_top, 2*i] = "(rest)"
            # fill counts
            df_count.iloc[:n_top-1, 2*i+1] = tmp.iloc[:n_top-1].values
            df_count.iloc[n_top-1, 2*i+1] = list(tmp.iloc[n_top-1:n_top_new].values)
            df_count.iloc[n_top, 2*i+1] = tmp.iloc[n_top_new:].values.sum()

    return df_count

输出:

生成一个human-readabletable。请注意,列 str.

的第 2、3 和 4 位是并列的
print(count_top_n(df, 3))
      str str_count       num num_count
1       V        52        71        51
2       Q        46        86        47
3  [B, K]  [46, 46]  [90, 67]  [46, 46]
4  (rest)       810    (rest)       810

使用以下函数:

def myFilter(col, maxOther = 0):
    unq = col.value_counts()
    if maxOther == 0:    # Return 3 MFV
        thr = unq.unique()[:3][-1]
        otherCnt = unq[unq < thr].sum()
        rv = col[col.isin(unq[unq >= thr].index)]
    else:    # Drop last LFV, no more than maxOther
        otherCnt = 0
        for i in unq[::-1]:
            if otherCnt + i >= maxOther: break
            otherCnt += i
        thrInd = unq.size - i + 1
        rv = col[col.isin(unq[:thrInd].index)]
    rv = rv.reset_index(drop=True)
    # print(f'  Trace {col.name}\nunq:\n{unq}\notherCnt: {otherCnt}')
    return rv

我的假设是两种变体之间的区别:

  • return 3 个最常见的值 (MFV),
  • 丢弃最后一个不太频繁的(其他)值

maxOther 参数控制。 它的默认值 0 表示“第一个变体”。

所以要测试这两个变体,请调用它:

  • df.apply(myFilter) 对于第一个变体,
  • df.apply(myFilter, maxOther=10) 第二个变体。

要查看跟踪打印输出,请取消注释 print 指令 在函数中。