Ful*_*lco 9 python numpy recommendation-engine pandas categorical-data
对于推荐服务,我正在一组用户 - 项目交互上训练矩阵分解模型(LightFM).为了使矩阵分解模型产生最佳结果,我需要将我的用户和项目ID映射到从0开始的连续范围的整数ID.
我在这个过程中使用了一个pandas DataFrame,我发现MultiIndex非常方便创建这个映射,如下所示:
ratings = [{'user_id': 1, 'item_id': 1, 'rating': 1.0},
{'user_id': 1, 'item_id': 3, 'rating': 1.0},
{'user_id': 3, 'item_id': 1, 'rating': 1.0},
{'user_id': 3, 'item_id': 3, 'rating': 1.0}]
df = pd.DataFrame(ratings, columns=['user_id', 'item_id', 'rating'])
df = df.set_index(['user_id', 'item_id'])
df
Out:
rating
user_id item_id
1 1 1.0
1 3 1.0
3 1 1.0
3 1 1.0
Run Code Online (Sandbox Code Playgroud)
然后允许我像这样获得连续的地图
df.index.labels[0] # For users
Out:
FrozenNDArray([0, 0, 1, 1], dtype='int8')
df.index.labels[1] # For items
Out:
FrozenNDArray([0, 1, 0, 1], dtype='int8')
Run Code Online (Sandbox Code Playgroud)
之后,我可以使用df.index.levels[0].get_loc方法将它们映射回来.大!
但是,现在我正在尝试简化我的模型训练过程,理想情况是通过逐步训练新数据,保留旧的ID映射.就像是:
new_ratings = [{'user_id': 2, 'item_id': 1, 'rating': 1.0},
{'user_id': 2, 'item_id': 2, 'rating': 1.0}]
df2 = pd.DataFrame(new_ratings, columns=['user_id', 'item_id', 'rating'])
df2 = df2.set_index(['user_id', 'item_id'])
df2
Out:
rating
user_id item_id
2 1 1.0
2 2 1.0
Run Code Online (Sandbox Code Playgroud)
然后,只需将新评级附加到旧DataFrame即可
df3 = df.append(df2)
df3
Out:
rating
user_id item_id
1 1 1.0
1 3 1.0
3 1 1.0
3 3 1.0
2 1 1.0
2 2 1.0
Run Code Online (Sandbox Code Playgroud)
看起来不错,但是
df3.index.labels[0] # For users
Out:
FrozenNDArray([0, 0, 2, 2, 1, 1], dtype='int8')
df3.index.labels[1] # For items
Out:
FrozenNDArray([0, 2, 0, 2, 0, 1], dtype='int8')
Run Code Online (Sandbox Code Playgroud)
我故意在后面的DataFrame中添加了user_id = 2和item_id = 2,以说明它出错的地方.在df3,标签3(用户和项目)已从整数位置1移动到2.因此映射不再相同.我正在寻找的是[0, 0, 1, 1, 2, 2]和[0, 1, 0, 1, 0, 2]分别为用户和项目映射.
这可能是因为pandas Index对象的排序,我不确定我想要的是使用MultiIndex策略.寻求有关如何最有效地解决这个问题的帮助:)
一些说明:
我已经对@jpp的答案进行了修改,以满足我后来添加的额外要求(用EDIT标记).这也真正满足了标题中提出的原始问题,因为它保留了旧的索引整数位置,无论出于何种原因重新排序行.我还把东西包装成了函数:
from itertools import chain
from toolz import unique
def expand_index(source, target, index_cols=['user_id', 'item_id']):
# Elevate index to series, keeping source with index
temp = source.reset_index()
target = target.reset_index()
# Convert columns to categorical, using the source index and target columns
for col in index_cols:
i = source.index.names.index(col)
col_cats = list(unique(chain(source.index.levels[i], target[col])))
temp[col] = pd.Categorical(temp[col], categories=col_cats)
target[col] = pd.Categorical(target[col], categories=col_cats)
# Convert series back to index
source = temp.set_index(index_cols)
target = target.set_index(index_cols)
return source, target
def concat_expand_index(old, new):
old, new = expand_index(old, new)
return pd.concat([old, new])
df3 = concat_expand_index(df, df2)
Run Code Online (Sandbox Code Playgroud)
结果:
df3.index.labels[0] # For users
Out:
FrozenNDArray([0, 0, 1, 1, 2, 2], dtype='int8')
df3.index.labels[1] # For items
Out:
FrozenNDArray([0, 1, 0, 1, 0, 2], dtype='int8')
Run Code Online (Sandbox Code Playgroud)
连接后强制对齐索引标签似乎并不简单,即使有解决方案,也很少有文档记录。
可能对您有吸引力的一种选项是分类数据。通过一些仔细的操作,这可以实现相同的目的:级别内的每个唯一索引值都具有到整数的一对一映射,并且即使在与其他数据帧串联之后,该映射仍然存在。
from itertools import chain
from toolz import unique
# elevate index to series
df = df.reset_index()
df2 = df2.reset_index()
# define columns for reindexing
index_cols = ['user_id', 'item_id']
# convert to categorical with merged categories
for col in index_cols:
col_cats = list(unique(chain(df[col], df2[col])))
df[col] = pd.Categorical(df[col], categories=col_cats)
df2[col] = pd.Categorical(df2[col], categories=col_cats)
# convert series back to index
df = df.set_index(index_cols)
df2 = df2.set_index(index_cols)
Run Code Online (Sandbox Code Playgroud)
我用来toolz.unique返回一个有序的唯一列表,但如果您无权访问该库,您可以使用文档unique_everseen中的相同配方。itertool
现在让我们看一下第 0 级索引级别下的类别代码:
for data in [df, df2]:
print(data.index.get_level_values(0).codes.tolist())
[0, 0, 1, 1]
[2, 2]
Run Code Online (Sandbox Code Playgroud)
然后执行我们的串联:
df3 = pd.concat([df, df2])
Run Code Online (Sandbox Code Playgroud)
最后,检查分类代码是否对齐:
print(df3.index.get_level_values(0).codes.tolist())
[0, 0, 1, 1, 2, 2]
Run Code Online (Sandbox Code Playgroud)
对于每个索引级别,请注意,我们必须将数据帧中的所有索引值进行并集以形成col_cats,否则串联将失败。