我有一个数据框,每一行都有一个列表值。
id list_of_value
0 ['a','b','c']
1 ['d','b','c']
2 ['a','b','c']
3 ['a','b','c']
我必须用一行计算所有其他行的得分
例如:
Step 1: Take value of id 0: ['a','b','c'],
Step 2: find the intersection between id 0 and id 1 ,
resultant = ['b','c']
Step 3: Score Calculation => resultant.size / id.size
在ID 0和ID 1,2,3之间重复步骤2,3,对所有ID都类似。
并创建一个N x N数据帧;像这样:
- 0 1 2 3
0 1 0.6 1 1
1 1 1 1 1
2 1 1 1 1
3 1 1 1 1
现在我的代码只有一个for循环:
def scoreCalc(x,queryTData):
#mathematical calculation
commonTData = np.intersect1d(np.array(x),queryTData)
return commonTData.size/queryTData.size
ids = list(df['feed_id'])
dfSim = pd.DataFrame()
for indexQFID in range(len(ids)):
queryTData = np.array(df.loc[df['id'] == ids[indexQFID]]['list_of_value'].values.tolist())
dfSim[segmentDfFeedIds[indexQFID]] = segmentDf['list_of_value'].apply(scoreCalc,args=(queryTData,))
有更好的方法吗?我可以只编写一个Apply函数而不是进行for循环迭代吗? 我可以更快吗?
答案 0 :(得分:7)
如果数据不是太大,可以使用get_dummies
对值进行编码并进行矩阵乘法:
s = pd.get_dummies(df.list_of_value.explode()).sum(level=0)
s.dot(s.T).div(s.sum(1))
输出:
0 1 2 3
0 1.000000 0.666667 1.000000 1.000000
1 0.666667 1.000000 0.666667 0.666667
2 1.000000 0.666667 1.000000 1.000000
3 1.000000 0.666667 1.000000 1.000000
更新:这是该代码的简短说明。主要思想是将给定的列表转换为一键编码:
a b c d
0 1 1 1 0
1 0 1 1 1
2 1 1 1 0
3 1 1 1 0
一旦有了,两行的交点大小,例如0
和1
只是它们的点积,因为当且仅当一个字符被表示时,它才属于两行由1
组成。
请记住,请首先使用
df.list_of_value.explode()
将每个单元格变成一个系列并将所有这些系列连接起来。输出:
0 a
0 b
0 c
1 d
1 b
1 c
2 a
2 b
2 c
3 a
3 b
3 c
Name: list_of_value, dtype: object
现在,我们在该系列上使用pd.get_dummies
将其转换为一个热编码的数据帧:
a b c d
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
1 0 0 0 1
1 0 1 0 0
1 0 0 1 0
2 1 0 0 0
2 0 1 0 0
2 0 0 1 0
3 1 0 0 0
3 0 1 0 0
3 0 0 1 0
如您所见,每个值都有自己的行。由于我们要将属于同一原始行的那些合并为一行,因此我们可以通过原始索引对其求和。因此
s = pd.get_dummies(df.list_of_value.explode()).sum(level=0)
提供我们想要的二进制编码的数据帧。下一行
s.dot(s.T).div(s.sum(1))
就像您的逻辑一样:s.dot(s.T)
按行计算点积,然后.div(s.sum(1))
按行除计数。
答案 1 :(得分:4)
尝试一下
IntentSender
输出
range_of_ids = range(len(ids))
def score_calculation(s_id1,s_id2):
s1 = set(list(df.loc[df['id'] == ids[s_id1]]['list_of_value'])[0])
s2 = set(list(df.loc[df['id'] == ids[s_id2]]['list_of_value'])[0])
# Resultant calculation s1&s2
return round(len(s1&s2)/len(s1) , 2)
dic = {indexQFID: [score_calculation(indexQFID,ind) for ind in range_of_ids] for indexQFID in range_of_ids}
dfSim = pd.DataFrame(dic)
print(dfSim)
您也可以按照以下步骤进行操作
0 1 2 3
0 1.00 0.67 1.00 1.00
1 0.67 1.00 0.67 0.67
2 1.00 0.67 1.00 1.00
3 1.00 0.67 1.00 1.00
答案 2 :(得分:3)
在集合s_list
的列表上使用嵌套列表推导。在列表理解中,使用intersection
操作检查重叠并获取每个结果的长度。最后,构造数据框,并将其除以df.list_of_value
s_list = df.list_of_value.map(set)
overlap = [[len(s1 & s) for s1 in s_list] for s in s_list]
df_final = pd.DataFrame(overlap) / df.list_of_value.str.len().to_numpy()[:,None]
Out[76]:
0 1 2 3
0 1.000000 0.666667 1.000000 1.000000
1 0.666667 1.000000 0.666667 0.666667
2 1.000000 0.666667 1.000000 1.000000
3 1.000000 0.666667 1.000000 1.000000
如果每个列表中都有重复的值,则应使用collections.Counter
而不是set
。我将样本数据id = 0更改为['a','a','c']
并将id = 1更改为['d','b','a']
sample df:
id list_of_value
0 ['a','a','c'] #changed
1 ['d','b','a'] #changed
2 ['a','b','c']
3 ['a','b','c']
from collections import Counter
c_list = df.list_of_value.map(Counter)
c_overlap = [[sum((c1 & c).values()) for c1 in c_list] for c in c_list]
df_final = pd.DataFrame(c_overlap) / df.list_of_value.str.len().to_numpy()[:,None]
Out[208]:
0 1 2 3
0 1.000000 0.333333 0.666667 0.666667
1 0.333333 1.000000 0.666667 0.666667
2 0.666667 0.666667 1.000000 1.000000
3 0.666667 0.666667 1.000000 1.000000
答案 3 :(得分:2)
已更新
由于提出了许多候选解决方案,因此进行时序分析似乎是个好主意。我按照OP的要求生成了一些具有12k行的随机数据,并与每个集合的3个元素保持一致,但扩展了可用于填充集合的字母的大小。可以对其进行调整以匹配实际数据。
让我知道您是否有想要测试或更新的解决方案。
设置
import pandas as pd
import random
ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
def random_letters(n, n_letters=52):
return random.sample(ALPHABET[:n_letters], n)
# Create 12k rows to test scaling.
df = pd.DataFrame([{'id': i, 'list_of_value': random_letters(3)} for i in range(12000)])
当前优胜者
def method_quang(df):
s = pd.get_dummies(df.list_of_value.explode()).sum(level=0)
return s.dot(s.T).div(s.sum(1))
%time method_quang(df)
# CPU times: user 10.5 s, sys: 828 ms, total: 11.3 s
# Wall time: 11.3 s
# ...
# [12000 rows x 12000 columns]
竞争者
def method_mcskinner(df):
explode_df = df.set_index('id').list_of_value.explode().reset_index()
explode_df = explode_df.rename(columns={'list_of_value': 'value'})
denom_df = explode_df.groupby('id').size().reset_index(name='denom')
numer_df = explode_df.merge(explode_df, on='value', suffixes=['', '_y'])
numer_df = numer_df.groupby(['id', 'id_y']).size().reset_index(name='numer')
calc_df = numer_df.merge(denom_df, on='id')
calc_df['score'] = calc_df['numer'] / calc_df['denom']
return calc_df.pivot('id', 'id_y', 'score').fillna(0)
%time method_mcskinner(df)
# CPU times: user 29.2 s, sys: 9.66 s, total: 38.9 s
# Wall time: 29.6 s
# ...
# [12000 rows x 12000 columns]
def method_rishab(df):
vals = [[len(set(val1) & set(val2)) / len(val1) for val2 in df['list_of_value']] for val1 in df['list_of_value']]
return pd.DataFrame(columns=df['id'], data=vals)
%time method_rishab(df)
# CPU times: user 2min 12s, sys: 4.64 s, total: 2min 17s
# Wall time: 2min 18s
# ...
# [12000 rows x 12000 columns]
def method_fahad(df):
ids = list(df['id'])
range_of_ids = range(len(ids))
def score_calculation(s_id1,s_id2):
s1 = set(list(df.loc[df['id'] == ids[s_id1]]['list_of_value'])[0])
s2 = set(list(df.loc[df['id'] == ids[s_id2]]['list_of_value'])[0])
# Resultant calculation s1&s2
return round(len(s1&s2)/len(s1) , 2)
dic = {indexQFID: [score_calculation(indexQFID,ind) for ind in range_of_ids] for indexQFID in range_of_ids}
return pd.DataFrame(dic)
# Stopped manually after running for more than 10 minutes.
带有解决方案详细信息的原始帖子
可以在pandas
中通过自连接进行此操作。
正如其他答案所指出的那样,第一步是将数据解压缩为更长的格式。
explode_df = df.set_index('id').list_of_value.explode().reset_index()
explode_df = explode_df.rename(columns={'list_of_value': 'value'})
explode_df
# id value
# 0 0 a
# 1 0 b
# 2 0 c
# 3 1 d
# 4 1 b
# ...
从此表中可以计算每个ID的计数。
denom_df = explode_df.groupby('id').size().reset_index(name='denom')
denom_df
# id denom
# 0 0 3
# 1 1 3
# 2 2 3
# 3 3 3
然后是自联接,发生在value
列上。这会为每个相交值将ID配对一次,因此可以对配对的ID进行计数以获得相交的大小。
numer_df = explode_df.merge(explode_df, on='value', suffixes=['', '_y'])
numer_df = numer_df.groupby(['id', 'id_y']).size().reset_index(name='numer')
numer_df
# id id_y numer
# 0 0 0 3
# 1 0 1 2
# 2 0 2 3
# 3 0 3 3
# 4 1 0 2
# 5 1 1 3
# ...
然后可以将这两个合并,并计算分数。
calc_df = numer_df.merge(denom_df, on='id')
calc_df['score'] = calc_df['numer'] / calc_df['denom']
calc_df
# id id_y numer denom score
# 0 0 0 3 3 1.000000
# 1 0 1 2 3 0.666667
# 2 0 2 3 3 1.000000
# 3 0 3 3 3 1.000000
# 4 1 0 2 3 0.666667
# 5 1 1 3 3 1.000000
# ...
如果您喜欢矩阵形式,则可以使用pivot
。如果数据稀疏,这将是一个更大的表示。
calc_df.pivot('id', 'id_y', 'score').fillna(0)
# id_y 0 1 2 3
# id
# 0 1.000000 0.666667 1.000000 1.000000
# 1 0.666667 1.000000 0.666667 0.666667
# 2 1.000000 0.666667 1.000000 1.000000
# 3 1.000000 0.666667 1.000000 1.000000
答案 4 :(得分:2)
此解决方案将有效处理list
或str
或其他int
中的任何大小的数据和任何类型的值,如果有的话还要注意重复的值
# dummy data
df = pd.DataFrame({'id': [0, 1, 2, 3], 'list_of_value': [['a','b','c'],['d','b','c'], ['a','b','c'], ['a','b','c']]})
# calculating the target values using list comprehension
vals = [[len(set(val1) & set(val2)) / len(val1) for val2 in df['list_of_value']] for val1 in df['list_of_value']]
# new resultant Dataframe
df = pd.DataFrame(columns=df['id'], data=vals)
在这种情况下,列表理解性能更好,是因为它不需要加载列表的append属性并在每次迭代时都将其作为函数调用。换句话说,一般而言,列表理解的执行速度更快,因为挂起和恢复一个函数的框架,或者在其他情况下,多个函数比按需创建列表要慢。
使用列表推导代替不构建列表的循环,无意义地累积一个无意义的值列表,然后将其丢弃,通常会因创建和扩展列表的开销而变慢。
结果:
id 0 1 2 3
0 1.000000 0.666667 1.000000 1.000000
1 0.666667 1.000000 0.666667 0.666667
2 1.000000 0.666667 1.000000 1.000000
3 1.000000 0.666667 1.000000 1.000000
执行时间:
import timeit
def function():
df = pd.DataFrame({'id': [0, 1, 2, 3], 'list_of_value': [['a','b','c'],['d','b','c'], ['a','b','c'], ['a','b','c']]})
vals = [[len(set(val1) & set(val2)) / len(val1) for val2 in df['list_of_value']] for val1 in df['list_of_value']]
df = pd.DataFrame(columns=df['id'], data=vals)
print(timeit.timeit(f'{function()}', number=1000000))
# 0.010986731999999999
答案 5 :(得分:1)
您可以将列表转换为集合,然后使用相交功能检查是否重叠:
(仅根据您的要求使用1个apply函数:-))
Selectors
答案 6 :(得分:1)
我会使用product
来获取所有组合。然后我们可以使用numpy.isin
和numpy.mean
进行检查:
from itertools import product
l = len(df)
new_df = pd.DataFrame(data = np.array(list(map(lambda arr: np.isin(*arr),
product(df['list_of_value'],
repeat=2))))
.mean(axis=1).reshape(l,-1),
index = df['id'],
columns=df['id'])
id 0 1 2 3
id
0 1.000000 0.666667 1.000000 1.000000
1 0.666667 1.000000 0.666667 0.666667
2 1.000000 0.666667 1.000000 1.000000
3 1.000000 0.666667 1.000000 1.000000
时间采样
%%timeit
l = len(df)
new_df = pd.DataFrame(data = np.array(list(map(lambda arr: np.isin(*arr),
product(df['list_of_value'],
repeat=2))))
.mean(axis=1).reshape(l,-1),
index = df['id'],
columns=df['id'])
594 µs ± 5.05 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
答案 7 :(得分:1)
应该很快,还要考虑列表中的重复项
... import itertools
... from collections import Counter
... a=df.list_of_value.tolist()
... l=np.array([len(Counter(x[0]) & Counter(x[1]))for x in [*itertools.product(a,a)]]).reshape(len(df),-1)
... out=pd.DataFrame(l/df.list_of_value.str.len().values[:,None],index=df.id,columns=df.id)
...
out
id 0 1 2 3
id
0 1.000000 0.666667 1.000000 1.000000
1 0.666667 1.000000 0.666667 0.666667
2 1.000000 0.666667 1.000000 1.000000
3 1.000000 0.666667 1.000000 1.000000
答案 8 :(得分:0)
是的!我们正在这里寻找笛卡尔积,该积在this答案中给出。 无需for循环或列表理解即可
让我们向数据帧df
添加一个新的重复值,使其看起来像这样:
df['key'] = np.repeat(1, df.shape[0])
df
list_of_values key
0 [a, b, c] 1
1 [d, b, c] 1
2 [a, b, c] 1
3 [a, b, c] 1
下一步与自身合并
merged = pd.merge(df, df, on='key')[['list_of_values_x', 'list_of_values_y']]
这是合并框架的外观:
list_of_values_x list_of_values_y
0 [a, b, c] [a, b, c]
1 [a, b, c] [d, b, c]
2 [a, b, c] [a, b, c]
3 [a, b, c] [a, b, c]
4 [d, b, c] [a, b, c]
5 [d, b, c] [d, b, c]
6 [d, b, c] [a, b, c]
7 [d, b, c] [a, b, c]
8 [a, b, c] [a, b, c]
9 [a, b, c] [d, b, c]
10 [a, b, c] [a, b, c]
11 [a, b, c] [a, b, c]
12 [a, b, c] [a, b, c]
13 [a, b, c] [d, b, c]
14 [a, b, c] [a, b, c]
15 [a, b, c] [a, b, c]
然后我们使用axis=1
values = merged.apply(lambda x: np.intersect1d(x[0], x[1]).shape[0] / len(x[1]), axis=1)
重塑形状以获取所需格式的值
values.values.reshape(4, 4)
array([[1. , 0.66666667, 1. , 1. ],
[0.66666667, 1. , 0.66666667, 0.66666667],
[1. , 0.66666667, 1. , 1. ],
[1. , 0.66666667, 1. , 1. ]])
希望这会有所帮助:)