我正试图修复这个数据集的一个问题。链接在这里。因此,我以这种方式加载数据集。
df = pd.read_csv('ratings.csv', sep='::', names=['user_id', 'movie_id', 'rating', 'timestamp'])
num_of_unique_users = len(df['user_id'].unique())
唯一用户数69878。如果我们输出数据集的最后几行。我们可以看到用户id大于69878。在这种情况下,缺少用户id。对于movie id也是如此。电影id的个数超过实际id的个数。
我只希望它匹配缺失的user_id与现有的user_id,不超过69878。例如,号码75167将成为唯一用户id的最后一个数字,即69878和电影id65133将变成10677最后一个唯一的电影id。
实际
user_id movie_id rating timestamp
0 1 122 5.0 838985046
1 1 185 5.0 838983525
2 1 231 5.0 838983392
3 1 292 5.0 838983421
4 1 316 5.0 838983392
... ... ... ... ...
10000044 71567 1984 1.0 912580553
10000045 71567 1985 1.0 912580553
10000046 71567 1986 1.0 912580553
10000047 71567 2012 3.0 912580722
10000048 71567 2028 5.0 912580344
需要
user_id movie_id rating timestamp
0 1 122 5.0 838985046
1 1 185 5.0 838983525
2 1 231 5.0 838983392
3 1 292 5.0 838983421
4 1 316 5.0 838983392
... ... ... ... ...
10000044 69878 1984 1.0 912580553
10000045 69878 1985 1.0 912580553
10000046 69878 1986 1.0 912580553
10000047 69878 2012 3.0 912580722
10000048 69878 2028 5.0 912580344
对熊猫有什么办法吗?
方法如下:
df2 = df.groupby('user_id').count().reset_index()
df2 = df2.assign(new_user_id=df2.index + 1).set_index('user_id')
df = df.join(df2['new_user_id'], on='user_id').drop(columns=['user_id']).rename(columns={'new_user_id':'user_id'})
df2 = df.groupby('movie_id').count().reset_index()
df2 = df2.assign(new_movie_id=df2.index + 1).set_index('movie_id')
df = df.join(df2['new_movie_id'], on='movie_id').drop(columns=['movie_id']).rename(columns={'new_movie_id':'movie_id'})
df = pd.concat([df[['user_id', 'movie_id']], df.drop(columns=['user_id', 'movie_id'])], axis=1)
样本输入:
user_id movie_id rating timestamp
0 1 2 5.0 838985046
1 1 4 5.0 838983525
2 3 4 5.0 838983392
3 3 6 5.0 912580553
4 5 2 5.0 912580722
5 5 6 5.0 912580344
样本输出:
user_id movie_id rating timestamp
0 1 1 5.0 838985046
1 1 2 5.0 838983525
2 2 2 5.0 838983392
3 2 3 5.0 912580553
4 3 1 5.0 912580722
5 3 3 5.0 912580344
中间结果及解释
首先我们这样做:
df2 = df.groupby('user_id').count().reset_index()
输出:
user_id movie_id rating timestamp
0 1 2 2 2
1 3 2 2 2
2 5 2 2 2
我们在上面所做的是使用groupby
来获取每个唯一user_id的一行。我们调用count
只是为了将输出(一个groupby对象)转换回数据帧。我们调用reset_index
来创建一个新的没有间隙的整数范围索引。(注意:我们唯一关心将来使用的列是user_id)
接下来我们这样做:
df2 = df2.assign(new_user_id=df2.index + 1).set_index('user_id')
输出:
movie_id rating timestamp new_user_id
user_id
1 2 2 2 1
3 2 2 2 2
5 2 2 2 3
assign
调用创建了一个名为new_user_id的新列,我们使用0偏移索引加1来填充它(这样我们就不会有id值<1).set_index
调用用user_id
替换我们的索引,预期使用该数据帧的索引作为稍后调用join
的目标。
下一步是:
df = df.join(df2['new_user_id'], on='user_id').drop(columns=['user_id']).rename(columns={'new_user_id':'user_id'})
输出:
movie_id rating timestamp user_id
0 2 5.0 838985046 1
1 4 5.0 838983525 1
2 4 5.0 838983392 2
3 6 5.0 912580553 2
4 2 5.0 912580722 3
5 6 5.0 912580344 3
这里我们只取了df2的new_user_id列,并在df对象上调用join
,指示方法使用df中的user_id列(on
参数)与索引(最初是df2中的user_id列)连接。这将在名为new_user_id的列中创建一个df,其中包含所需的新范式user_id值。剩下的就是删除旧范式的user_id列,并将new_user_id重命名为user_id,这就是对drop
和rename
的调用所做的。
将movie_id值更改为新范式的逻辑(即,消除唯一值集中的空白)是完全类似的。当我们完成后,我们有这样的输出:
rating timestamp user_id movie_id
0 5.0 838985046 1 1
1 5.0 838983525 1 2
2 5.0 838983392 2 2
3 5.0 912580553 2 3
4 5.0 912580722 3 1
5 5.0 912580344 3 3
最后,我们使用以下代码重新排序列,使其看起来像原始的:
df = pd.concat([df[['user_id', 'movie_id']], df.drop(columns=['user_id', 'movie_id'])], axis=1)
输出:
user_id movie_id rating timestamp
0 1 1 5.0 838985046
1 1 2 5.0 838983525
2 2 2 5.0 838983392
3 2 3 5.0 912580553
4 3 1 5.0 912580722
5 3 3 5.0 912580344
更新:这里有一个替代的解决方案,使用Series.unique()
代替gropuby
,节省了几行:
df2 = pd.DataFrame(df.user_id.unique(), columns=['user_id']
).reset_index().set_index('user_id').rename(columns={'index':'new_user_id'})['new_user_id'] + 1
df = df.join(df2, on='user_id').drop(columns=['user_id']).rename(columns={'new_user_id':'user_id'})
df2 = pd.DataFrame(df.movie_id.unique(), columns=['movie_id']
).reset_index().set_index('movie_id').rename(columns={'index':'new_movie_id'})['new_movie_id'] + 1
df = df.join(df2, on='movie_id'
).drop(columns=['movie_id']).rename(columns={'new_movie_id':'movie_id'})
df = pd.concat([df[['user_id', 'movie_id']], df.drop(columns=['user_id', 'movie_id'])], axis=1)
这里的思路是:
1号线:
- 使用
unique
来获取user_id的唯一值,而无需计数重复或维护其他列(这是groupby
在上面的原始解决方案中所做的) - 在名为new_user_id 的列中创建一个包含这些唯一值的新数据框
- 调用
reset_index
获取索引,该索引是一个非间隙整数范围(一个整数为每个唯一的user_id) - 调用
set_index
,它将创建一个名为'index'的列,其中包含前面的索引(0..)并使user_id作为新的索引 - 将"index"列重命名为new_user_id
- 访问new_user_id列,并添加1将id值从0-offset转换为1-offset。
2行:
- 调用
join
,就像我们在原来的解决方案中所做的那样,除了other
数据帧只是df2(这很好,因为它只有一个列,new_user_id)。
movie_id的逻辑完全类似,使用concat
的最后一行与上面的原始解决方案相同。