我有一个函数来查找给定列表(一个列表(和其他列表之间的常见、不常见项目及其速率 每个用户(4,000 个用户(的列表(60,000 个列表(。在循环下方运行需要太长时间且使用率高 与部分列表构建和崩溃。我认为由于返回的列表很长和重元素(元组(, 所以我把它分成两个函数,如下所示,但在元组中附加列表项似乎是问题,[(user, [items],rate),(user, [items],rate),....]
.我想从返回的值创建一个数据帧,
我应该对算法做些什么来解决这个问题并减少内存使用量?
我正在使用python 3.7,Windows 10,64位,RAM 8G。
常用物品功能:
def common_items(user,list1, list2):
com_items = list(set(list1).intersection(set(list2)))
com_items_rate = len(com_items)/len(set(list1).union(set(list2)))
return user, com_items, com_items_rate
不常见物品功能:
def uncommon_items(user,list1, list2):
com_items = list(set(list1).intersection(set(list2)))
com_items_rate = len(com_items)/len(set(list1).union(set(list2)))
uncom_items = list(set(list2) - set(com_items)) # uncommon items that blonge to list2
uncom_items_rate = len(uncom_items)/len(set(list1).union(set(list2)))
return user, com_items_rate, uncom_items, uncom_items_rate # common_items_rate is also needed
构建列表:
common_item_rate_tuple_list = []
for usr in users: # users.shape = 4,000
list1 = get_user_list(usr) # a function to get list1, it takes 0:00:00.015632 or less for a user
# print(usr, len(list1))
for list2 in df["list2"]: # df.shape = 60,000
common_item_rate_tuple = common_items(usr,list1, list2)
common_item_rate_tuple_list.append(common_item_rate_tuple)
print(len(common_item_rate_tuple_list)) # 4,000 * 60,000 = 240,000,000 items
# sample of common_item_rate_tuple_list:
#[(1,[2,5,8], 0.676), (1,[7,4], 0.788), ....(4000,[1,5,7,9],0.318), (4000,[8,9,6],0.521)
我查看了(内存错误和列表限制? (在 Python 中追加到列表时的内存错误(它们处理构造的列表。我无法处理建议的答案(Python列表内存错误(。
对于这么大的数据进行速度和内存管理,您应该考虑几件事。
- 您正在或应该在这里只使用
sets
,因为顺序在您的列表中没有意义,并且您正在做很多集合的交叉。 那么,您可以更改get_user_list()
函数以返回集合而不是列表吗? 这将防止您正在进行的所有不必要的转换。 与 list2 相同,只需立即制作一组即可 - 在寻找"不常见的项目"时,您应该只在集合上使用对称差分运算符。 更快,更少的列表>集合转换
- 在循环结束时,您真的要创建一个包含 240M 子列表的列表吗? 那可能是你的记忆爆炸。 我会建议使用带有键作为用户名的字典。 并且只有在有公共项目的情况下,您才需要在其中创建一个条目。 如果有"稀疏"匹配,您将获得一个非常小的数据容器
--- 编辑示例
所以我认为你希望把它保存在数据框中太大了。 也许您可以执行所需的操作,而无需将其存储在数据框中。 字典是有道理的。 您甚至可以"动态"计算事物,而不存储数据。 无论如何。 这是一个玩具示例,显示了使用 4K 用户和 10K "其他列表"的内存问题。 当然,相交集合的大小可能会有所不同,但它是有益的:
import sys
import pandas as pd
# create list of users by index
users = list(range(4000))
match_data = list()
size_list2 = 10_000
for user in users:
for t in range(size_list2):
match_data.append(( user, (1,5,6,9), 0.55)) # 4 dummy matches and fake percentage
print(match_data[:4])
print(f'size of match: {sys.getsizeof(match_data)/1_000_000} MB')
df = pd.DataFrame(match_data)
print(df.head())
print(f'size of dataframe {sys.getsizeof(df)/1_000_000} MB')
这将产生以下结果:
[(0, (1, 5, 6, 9), 0.55), (0, (1, 5, 6, 9), 0.55), (0, (1, 5, 6, 9), 0.55), (0, (1, 5, 6, 9), 0.55)]
size of match: 335.072536 MB
0 1 2
0 0 (1, 5, 6, 9) 0.55
1 0 (1, 5, 6, 9) 0.55
2 0 (1, 5, 6, 9) 0.55
3 0 (1, 5, 6, 9) 0.55
4 0 (1, 5, 6, 9) 0.55
size of dataframe 3200.00016 MB
您可以看到,对于仅 10K 其他列表的想法,简而言之,数据帧中的 3.2GB。 这将是无法管理的。
这是一个数据结构的想法,只是为了一直使用字典。
del df
# just keep it in a dictionary
data = {} # intended format: key= (usr, other_list) : value= [common elements]
# some fake data
user_items = { 1: {2,3,5,7,99},
2: {3,5,88,790},
3: {2,4,100} }
# some fake "list 2 data"
list2 = [ {1,2,3,4,5},
{88, 100},
{11, 13, 200}]
for user in user_items.keys():
for idx, other_set in enumerate(list2): # using enumerate to get the index of the other list
common_elements = user_items.get(user) & other_set # set intersection
if common_elements: # only put it into the dictionary if it is not empty
data[(user, idx)] = common_elements
# try a couple data pulls
print(f'for user 1 and other list 0: {data.get((1, 0))}')
print(f'for user 2 and other list 2: {data.get((2, 2))}') # use .get() to be safe. It will return None if no entry
此处的输出为:
for user 1 and other list 0: {2, 3, 5}
for user 2 and other list 2: None
如果您要大量处理这些数据,您的另一种选择就是将这些表放入像sqlite
这样的数据库中,该数据库是内置的,不会消耗您的内存。