我有一本装满字典的字典。它可能看起来像这样:
import pandas as pd
my_dict = {'gs_1': set(('ENS1', 'ENS2', 'ENS3')),
'gs_2': set(('ENS1', 'ENS4', 'ENS5', 'ENS7', 'ENS8')),
'gs_3': set(('ENS2', 'ENS3', 'ENS6'))}
我还构建了一个熊猫数据框,看起来像这样:
my_df = pd.DataFrame(columns=my_dict.keys())
my_df.gs_1=[0, 0, 0]
my_df.gs_2=[0, 0, 0]
my_df.gs_3=[0, 0, 0]
my_df.index = my_dict.keys()
my_df
收益
gs_1 gs_2 gs_3
gs_1 0 0 0
gs_2 0 0 0
gs_3 0 0 0
我的目标是尽可能有效地使用每个集合之间的交点长度填充DataFrame。严格来说,不必先构建DataFrame然后再填充它。现在,我的解决方案是:
for gs_1 in my_df.index:
for gs_2 in my_df.columns:
my_df.loc[gs_1, gs_2] = len(my_dict[gs_1] & my_dict[gs_2])
my_df
正确地屈服
gs_1 gs_2 gs_3
gs_1 3 1 2
gs_2 1 5 0
gs_3 2 0 3
我的问题是,这太慢了。实际上,gs_n可扩展到6000左右,而我为此预计的运行时间接近2小时。去这里最好的方法是什么?
答案 0 :(得分:3)
这是我基于scipy.spatial.distance_matrix
的方法:
# create unions of values
total = set()
for key, val in my_dict.items():
total = total.union(val)
total = list(total)
# create data frame
df = pd.DataFrame({}, index=total)
for key, val in my_dict.items():
df[key] = pd.Series(np.ones(len(val)), index=list(val))
df = df.fillna(0).astype(bool)
# return result:
x = df.values
np.sum(x[:,np.newaxis,:]&x[:,:,np.newaxis], axis=0)
#array([[3, 1, 2],
# [1, 5, 0],
# [2, 0, 3]], dtype=int32)
# if you want a data frame:
new_df = pd.DataFrame(np.sum(x[:,np.newaxis,:]&x[:,:,np.newaxis],
axis=0),
index=df.columns, columns=df.columns)
将11s取为6000 gs_
和100个唯一值:
max_total = 100
my_dict = {}
for i in range(6000):
np.random.seed(i)
sample_size = np.random.randint(1,max_total)
my_dict[i] = np.random.choice(np.arange(max_total), replace=False, size=sample_size)
编辑:如果您有大量唯一值,则可以处理较小的子集并将其加起来。像这样:
chunk_size = 100
ans = np.zeros(num_gs, num_gs)
for x in range(0, len(total), chunk_size):
chunk = total[x:x+chunk_size]
df = pd.DataFrame({}, index=chunk)
for key, val in my_dict.items():
sub_set = val.intersection(set(chunk))
df[key] = pd.Series(np.ones(len(sub_set )), index=list(sub_set ))
df = df.fillna(0).astype(bool)
# return result:
x = df.values
ans += np.sum(x[:,np.newaxis,:]&x[:,:,np.newaxis], axis=0)
使用14000个唯一值,则大约需要140 * 15 = 2000秒。没那么快,但是明显少于2小时:-)。
如果内存允许,您还可以增加chunk_size
。那是我的8GB Ram系统的限制:-)。
此外,还可以对子集(chunk
)进行并行化。
答案 1 :(得分:1)
Quang的解决方案效果很好,但是当我尝试付诸实践时却失败了。即使有了分块解决方案,我在最后一步也遇到了内存问题:
ans += np.sum(x[:,np.newaxis,:]&x[:,:,np.newaxis], axis=0)
我决定采用另一种方法,并且设法找到一种解决该问题的解决方案,该解决方案在解决该问题时既更快又更有效:
import pandas as pd
import itertools
import numpy as np
my_dict = {'gs_1': set(('ENS1', 'ENS2', 'ENS3')),
'gs_2': set(('ENS1', 'ENS4', 'ENS5', 'ENS7', 'ENS8')),
'gs_3': set(('ENS2', 'ENS3', 'ENS6'))}
gs_series = pd.Series({a:b for a,b in zip(itertools.combinations_with_replacement(my_dict.keys(),2),
[len(c&d) for c,d in itertools.combinations_with_replacement(my_dict.values(),2)])})
gs_df = gs_series.unstack()
proper_index = gs_series.index.get_level_values(0).unique()
gs_df = gs_df.reindex(proper_index)[proper_index.values].copy()
i_lower = np.tril_indices(np.array(len(gs_df.columns)), -1)
gs_matrix = gs_df.values
gs_matrix[i_lower] = gs_matrix.T[i_lower]
gs_df
这会正确产生
gs_1 gs_2 gs_3
gs_1 3.0 1.0 2.0
gs_2 1.0 5.0 0.0
gs_3 2.0 0.0 3.0
基本思想是使用itertools
构建一个字典,其中每两个集合之间的交点的长度为1,然后将其转换为pd.Series
。 itertools.combinations_with_replacement
执行一次每个比较,因此在pd.Series
堆积之后,我们得到了矩阵的(无序)右上三角形。按原始索引对行和列进行排序将使我们得到一个正确填充的右上三角,剩下要做的就是将其反映到矩阵的左下三角上。我最后使用了约8 GB的RAM进行5200x5200矩阵比较,其中每个集合中约有17000个可能的唯一值可填充,每个集合中包含10-1000个唯一值。几分钟就完成了。