将数据框中的用户(在多行上重复)和项目转换为标签二值化数据框

Posted

技术标签:

【中文标题】将数据框中的用户(在多行上重复)和项目转换为标签二值化数据框【英文标题】:Transform users (repeated over multiple rows) and items in a dataframe into a label binarized dataframe 【发布时间】:2020-02-13 17:00:01 【问题描述】:

我有一个像这样的 DataFrame

df = pd.DataFrame([
    ['a', 1], 
    ['b', 1],
    ['c', 1],
    ['a', 2], 
    ['c', 3], 
    ['b', 4], 
    ['c', 4]
], columns=['item', 'user'])

每个用户在多行中重复(使用不同的项目)。

我想执行 LabelEncoder/LabelBinarizer 之类的转换 (??) 以将 DataFrame 转换为如下所示的内容:

pd.DataFrame([
    [1, 1, 1], #user 1
    [1, 0, 0], #user 2
    [0, 0, 1], #user 3
    [0, 1, 1]  #user 4
], columns=['a', 'b', 'c'])

我可能不想使用 pandas(pivotget_dummiescrosstab),因为我想将新用户传递给转换器:

new_user = pd.DataFrame([
    ['c', 5], 
    ['d', 5]
], columns=['item', 'user'])

然后得到类似这样的东西:

[0, 0, 1]

重要:解决方案必须解决新的用户案例(和删除的“d”项),并保留列顺序和维度

【问题讨论】:

可以预先定义项目列表吗? @DanielMesejo 当然!我正在想象一个fit() 步骤,它将记住所有可能项目的列表(有点像 LabelEncoder) @emehex 查看我的答案。修复了更多错误。抱歉 jupyer 使用了以前的变量。重启后发现bug。顺便说一句,好问题。 【参考方案1】:

带有一些标准 scikit-learn 的解决方案:

from sklearn.feature_extraction.text import CountVectorizer

def squish(df, user='user', item='item'):
    df = df.groupby([user])[item].apply(lambda x: ','.join(x))
    X = pd.DataFrame(df)[item]
    return X

cv = CountVectorizer(tokenizer=lambda x: x.split(','))
X = squish(df)
cv.fit_transform(X).todense()

这将产生:

# matrix([[1, 1, 1],
#         [1, 0, 0],
#         [0, 0, 1],
#         [0, 1, 1]], dtype=int64)

它还解决了新的用户案例:

new_user = pd.DataFrame([
    ['c', 5],
    ['d', 5]
], columns=['item', 'user'])

X_new = squish(new_user)
cv.transform(X_new).todense()

正确屈服:

# matrix([[0, 0, 1]])

【讨论】:

【参考方案2】: 天啊。这是我想出的。 长链。我会分解的。
import pandas as pd
def encode(l):
    return pd.DataFrame(l, columns=['item', 'user'])['item'].unique()

# create dataframe
# group by and get dummies
# remove unncessary colums which are not part of encoding class
# apply to create list
def add_user(l, _key_):
    return  pd.DataFrame(l, columns=['item', 'user']).\
            groupby('user')['item'].apply('|'.join).str.get_dummies().\
            reindex(columns=_key_).fillna(0).astype('int').\
            apply(lambda x: list(x), axis=1)

_key_ = encode ([
    ['a', 1], 
    ['b', 1],
    ['c', 1],
    ['a', 2], 
    ['c', 3], 
    ['b', 4], 
    ['c', 4]
])
add_user([
    ['a', 1], 
    ['b', 1],
    ['c', 1],
    ['a', 2], 
    ['c', 3], 
    ['b', 4], 
    ['c', 4]
], _key_)

输出:

user
1    [1, 1, 1]
2    [1, 0, 0]
3    [0, 0, 1]
4    [0, 1, 1]
add_user([['b',5],['d', 5]], _key_)

输出:

user
5    [0, 1, 0]
encode 将为您的编码器生成初始 keysadd_user你可以为每个新用户调用这个函数。 请注意,您可以通过reset_index 获取user 列。

解决方案 2:

灵感来自 @WeNYoBen 的回答。
import pandas as pd
df = pd.DataFrame([
    ['a', 1], 
    ['b', 1],
    ['c', 1],
    ['a', 2], 
    ['c', 3], 
    ['b', 4], 
    ['c', 4]
], columns=['item', 'user'])
_key_ = df.item.unique()
def add_user(l, _key_):
    df = pd.DataFrame(l, columns=['item','user'])
    return pd.crosstab(df.user, df.item).reindex(columns=_key_.tolist()).fillna(0).astype('int').apply(list, axis=1)

add_user([['b',5],['d', 5]], _key_)
add_user 函数的不可读版本。
def add_user(l, _key_):
    return pd.crosstab(*[[list(x)] for x in list(zip(*l))[::-1]]).reindex(columns=_key_.tolist()).fillna(0).astype('int').apply(list, axis=1)

【讨论】:

嘿@Poojan,感谢您的工作!我一直在考虑这个问题,只是发布了我找到的解决方案。可能会有一些兴趣! @emehex 这是一个很好的解决方案。但是由于您没有提到我在 pandas 上提供的 sklearn 的使用。【参考方案3】:

对于这个问题,我会创建一个类Encoder,如下所示:

class Encoder:

    def __init__(self):
        self.items = None

    def transform(self, lst):
        """Returns a dictionary where the keys are the users_ids and the values are the encoded items"""
        if self.items is None:
            self.items = self.__items(lst)

        users = 
        for item, user in lst:
            users.setdefault(user, set()).add(item)

        return user: np.array([item in basket for item in self.items], dtype=np.uint8) for user, basket in users.items()

    def reset(self):
        self.items = None

    @staticmethod
    def __items(lst):
        seen = set()
        items = []
        for item, _ in lst:
            if item not in seen:
                items.append(item)
                seen.add(item)
        return items

然后,你可以这样使用它:

encoder = Encoder()
result = encoder.transform(df.values.tolist())  # here df is your original DataFrame
df_result = pd.DataFrame(data=result.values(), columns=encoder.items, index=result.keys())
print(df_result)

输出

   a  b  c
1  1  1  1
2  1  0  0
3  0  0  1
4  0  1  1

注意df_result 中的索引是用户。那么新的案例可以这样处理:

new_user = pd.DataFrame([
    ['c', 5],
    ['d', 5]
], columns=['item', 'user'])
new_user_result = encoder.transform(new_user.values.tolist())
print(pd.DataFrame(data=new_user_result.values(), columns=encoder.items, index=new_user_result.keys()))

输出

   a  b  c
5  0  0  1

至少在我看来,接收列表并返回字典是一种更灵活的方法。同样返回字典将处理用户不是连续整数的情况(例如,它们可以是 UUID)。最后在Encoder 类中,您还有一个重置方法,主要是为了忘记这些项目。

【讨论】:

嘿@Daniel Mesejo,我一直在仔细考虑这个问题,刚刚发布了我找到的解决方案。可能有点意思!

以上是关于将数据框中的用户(在多行上重复)和项目转换为标签二值化数据框的主要内容,如果未能解决你的问题,请参考以下文章

将数据框中的因子列转换为数字类型列[重复]

使用 pyspark 将 Spark 数据框中的列转换为数组 [重复]

使用较低的函数将pyspark数据框中单列中的值转换为文本清理中的小写[重复]

将数据从多行转换为多列[重复]

如何将多行标签xml文件转换为数据框

Python - 将数据框中的所有项目转换为字符串