用于字符串的 KBinsDiscretizer

问题描述

KBinsDiscretizer 分箱数字数据。

我有 string 数据整个 value_counts 看起来像这样:

MTS RUS                   495
Tele2                     484
MegaFon                   437
Beeline                   431
Vodafone UA               402
                         ... 
3 Austria                   1
FJ VODAFONE | Vodafone      1
Babilon-M                   1
MOOV BENIN                  1
3 | Beeline                 1
Name: carrier,Length: 822,dtype: int64

这已经是分类的了,但是有太多不同的值(822!)。

我想用相同的高度分箱(如 strategy="quantile" 中的 KBinsdiscretizer)将其分箱为很少的不同值 (5-10)。

算法很简单:将KBinsdiscretizer应用于转换为value_counts中值的数值等级的数据。

我想知道是否已经有一种方法可以做到(如果没有,如何按照惯用方式做到这一点)。

附注。一个关键的限制是该方法必须是“通用的”,我不能检查每个字符串列,我必须单独优化分箱。

解决方法

这是我拼凑的东西,它似乎有效,但我希望我可以使用 OOTB 的东西:

import sklearn
from sklearn.base import TransformerMixin,BaseEstimator

def only_column(df):
    if df.shape[1] != 1:
        raise ValueError("only_column",df.shape)
    return df[df.columns[0]]

class ObjectDiscretizer(TransformerMixin,BaseEstimator):
    def __init__(self,**kwargs):
        self.kbin_discr = sklearn.preprocessing.KBinsDiscretizer(**kwargs)
        self.name2rank = None
        self.names = None
        self.ranks = None
    
    def __str__(self):
        if self.name2rank is None:
            return "%s(%s)" % (self.__class__.__name__,self.kbin_discr)
        return "%s(n=%d,b=%d,%s)" % (
            self.__class__.__name__,len(self.name2rank),len(self.kbin_discr.bin_edges_),self.kbin_discr)

    def fit(self,X,y=None):
        "Learn how to discretize the data."
        col = only_column(X).astype(str)
        # np.unique does not always work
        # https://github.com/numpy/numpy/issues/18288
        vc = col.value_counts(dropna=False)
        self.name2rank = {v:i for i,v in enumerate(vc.index)}
        self.names,self.ranks = np.array(list(zip(*sorted(self.name2rank.items()))))
        self.kbin_discr.fit(col.replace(self.name2rank).values.reshape((-1,1)))
        return self
    
    def transform(self,X):
        "Discretize the data."
        return self.kbin_discr.transform(self.ranks[np.searchsorted(self.names,X.astype(str))]).astype(int)