将数据框中的用户(重复多行)和项目转换为标签二值化数据框
Transform users (repeated over multiple rows) and items in a dataframe into a label binarized dataframe
我有一个如下所示的 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(pivot
、get_dummies
、crosstab
),因为我想将新用户传递给转换器:
new_user = pd.DataFrame([
['c', 5],
['d', 5]
], columns=['item', 'user'])
然后像这样返回:
[0, 0, 1]
重要:解决方案必须解决新的用户案例(并删除 'd' 项),并保留列顺序和维度
- 天哪。这是我想出的。
- 长链接。我会分解它。
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
将为您的编码器生成初始 keys
。
add_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)
对于这个问题,我会创建一个 class 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
中的索引是用户。那么新的case可以这样处理:
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
class中,你还有一个重置方法,本质上是忘记项。
使用一些标准 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]])
我有一个如下所示的 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(pivot
、get_dummies
、crosstab
),因为我想将新用户传递给转换器:
new_user = pd.DataFrame([
['c', 5],
['d', 5]
], columns=['item', 'user'])
然后像这样返回:
[0, 0, 1]
重要:解决方案必须解决新的用户案例(并删除 'd' 项),并保留列顺序和维度
- 天哪。这是我想出的。
- 长链接。我会分解它。
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
将为您的编码器生成初始keys
。add_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)
对于这个问题,我会创建一个 class 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
中的索引是用户。那么新的case可以这样处理:
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
class中,你还有一个重置方法,本质上是忘记项。
使用一些标准 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]])