将列表与 DataFrame 中的每条记录进行比较
Posted
技术标签:
【中文标题】将列表与 DataFrame 中的每条记录进行比较【英文标题】:Comparing lists with every record in DataFrame 【发布时间】:2020-06-15 05:24:45 【问题描述】:我有一个用例,我将同一列中的列表与其自身进行比较,代码如下:
for i in range(0,len(counts95)):
for j in range(i+1,len(counts95)):
for x in counts95['links'][i]:
for y in counts95['links'][j]:
if x == y and counts95['linkoflinks'][j] is None:
counts95['linkoflinks'][j] = counts95['index'][i]
代码可以运行,但它对 python 不友好(使用 4 个 for 循环)并且需要大量时间来执行操作。 它背后的主要思想是将列表中的元素 counts95['links'] 的记录链接在任何后续行中,如果是,则将列 linksoflinks 更新为仅当 linksoflinks 列为 None (不覆盖)
时,第一列的索引找到下面的参考表:
counts95 = pd.DataFrame('index': [616351, 616352, 616353,6457754],
'level0': [25,30,35,100],
'links' : [[1,2,3,4,5],[23,45,2],[1,19,67],[14,15,16]],
'linksoflinks' : [None,None,None,None])
编辑: 新数据框
counts95 = pd.DataFrame('index': [616351, 616352, 616353,6457754,6566666,464664683],
'level0': [25,30,35,100,200,556],
'links' : [[1,2,3,4,5],[23,45,2],[1,19,67],[14,15,16],[1,14],[14,1]],
'linksoflinks' : [None,None,None,None,None,None])
期望的输出:
index level0 links linksoflinks
0 616351 25 [1, 2, 3, 4, 5] NaN
1 616352 30 [23, 45, 2] 616351.0
2 616353 35 [1, 19, 67] 616351.0
3 6457754 100 [14, 15, 16] NaN
4 6566666 200 [1,14] 616351.0
5 6457754 556 [14,1] 616351.0
【问题讨论】:
请分享数据而不是图片。 ***.com/questions/20109391/… 编辑问题以包含参考表示例 【参考方案1】:最好的模式是为您的任务使用适当的数据结构。回答“Y 序列中是否存在元素 X”问题的最佳选择是内置的set
。如果您的集合是不可变的,请考虑使用frozenset
。
解决方案
以下是我将如何以 Python 方式解决问题:
# necessary imports
from collections import defaultdict
from typing import Tuple, FrozenSet, DefaultDict
# initialise the links "mapping": for every index save frozenset of its links
links: Tuple[Tuple[int, FrozenSet[int]]] = (
# tuple of tuples is like a dict but will let you iterate by index
(616351, frozenset((1, 2, 3, 4, 5))),
(616352, frozenset((23, 45, 2))),
(616353, frozenset((1, 19, 67))),
(6457754, frozenset((14, 15, 16))),
)
# defaultdict automatically creates new lists
# as you access its keys which are not yet present
links_of_links: DefaultDict[int, List[int]] = defaultdict(list)
for i, item in enumerate(links):
key, values = item # split tuple into individual elements
next_rows = links[i+1:] # we will iterate over succeeding rows
for next_key, next_values in next_rows:
# here we check sets intersection:
# it is non-empty if any common elements are present
if values & next_values:
# though key might not be present in links_of_links,
# defaultdict will autocreate a new empty list
links_of_links[key].append(next_key)
links_of_links 的内容:defaultdict(<class 'list'>, 616351: [616352, 616353])
复杂性
现在让我们比较一下您和我的解决方案的复杂性,以证明后者更有效。假设N
是行数,L
是链接列表的某种长度(平均值或最大值,这并不重要)。您的解决方案大致比较了所有行对,这给了我们O(N * N)
。然后乘以两个列表的简单比较的复杂性 - O(L * L)
。它总共给了我们O(N * L)²
。
建议的解决方案仍然交叉连接所有行,因此N * N
留在我们身边。但现在我们以更有效的方式比较集合本身:O(min(L, L)) === O(L)
,正如Python Time Complexity 所说。所以整体复杂度除以单个L
,得到O(N² * L)
。
【讨论】:
这提供了所需的输出,但我怎样才能更改我预先存在的 Dataframe 以使用 freezesets? @Rishi 在我看来使用frozensets 没有问题,pandas 保留了它的类型:pd.DataFrame('x': frozenset([1, 2])).iloc[0]['x']
是frozenset(1, 2)
在问题中使用更新的数据集,这没有给出所需的输出,我还在问题中添加了所需的输出以供参考【参考方案2】:
使用explode
和duplicated
和.map
分配给重复的链接值,但只分配后者。
df = counts95.explode('links')
m = df[df.duplicated(subset=['links'],keep=False)].groupby('links')['index'].first()
df['link_above'] = df['links'].loc[df.duplicated(subset='links',keep='first')].map(m)
re_made_df = df.groupby(["index", "level0"]).agg(
links=("links", list), linkoflist=("link_above", "first")).reset_index()
print(re_made_df)
index level0 links linkoflist
0 616351 25 [1, 2, 3, 4, 5] NaN
1 616352 30 [23, 45, 2] 616351.0
2 616353 35 [1, 19, 67] 616351.0
3 6457754 100 [14, 15, 16] NaN
【讨论】:
这里的link_above是什么? 这是你的专栏,我只是在重新创建它之前使用了一个差异名称@rishi 你能解释一下这段代码吗,就像你在这里做的一步一步的过程,这对我有很大帮助。我看到这是可行的,但我必须理解它,而不仅仅是复制粘贴。 @Rishi 只是在一个项目的中间,但会做,它是否适用于您的解决方案?现在只需逐行打印以查看发生了什么并阅读explode
和map
的文档,我会尽快回复您
感谢您的帮助,但有一个问题,如果存在具有两个不同索引的重复列表,则会出现错误:无法从重复轴重新索引,我正在编辑问题以再添加 2 行可以重新创建错误。【参考方案3】:
与示例数据框构造函数相比,您所需的输出使用不同的值和列名。我使用您想要的输出数据框进行测试。
逻辑:
对于links
的每个子列表,我们需要找到第一个重叠子列表的行索引(我的意思是数据帧的索引,而不是列index
)。我们将使用这些行索引在counts95
上按.loc
切片,以获得index
列的对应值。为了实现这个目标,我们需要做几个步骤:
link
中的所有子列表进行比较。列表理解是
快速高效地完成这项任务。我们需要编写一个列表
理解创建布尔二维掩码数组,其中每个子数组
包含重叠行的True
值和非重叠行的False
2D-mask 并检查links
列,您会看得更清楚)
我们希望从顶部与当前子列表进行比较。 IE。常设
从当前行开始,我们只想向后比较到顶部。
因此,我们需要将任何前向比较设置为False
。这是
np.tril
的功能
在这个 2D 掩码的每个子数组中,True
的位置/索引是
当前子列表重叠的行的行索引。我们需要找到
True
的这些职位。这是np.argmax
的功能。
np.argmax
返回数组第一个最大元素的位置/索引。 True
被视为1
和False
被视为0
。所以,
在任何具有True
的子数组上,它会正确返回第一个重叠行索引。但是,在所有 False
子数组上,它返回 0
。稍后我们将使用where
处理所有False
子数组
np.argmax
之后,2D-mask 被缩减为 1D-mask。的每个元素
这个一维掩码是重叠子列表的行索引数。
将其传递给.loc
以获取列index
的相应值。
但是,结果也错误地包括了子数组所在的行
2D-mask 包含所有False
。我们希望这些行转到NaN
。它是
.where
的功能
方法一:
使用列表推导在links
的每个列表和links
中的所有列表之间构造布尔二维掩码m
。我们只需要向后比较,所以使用np.tril
将掩码的右上三角压碎为所有False
,表示向前比较。最后,调用np.argmax
获取m
每一行中第一个True
的位置,并链接where
将m
的所有False
行转换为NaN
c95_list = counts95.links.tolist()
m = np.tril([[any(x in l2 for x in l1) for l2 in c95_list] for l1 in c95_list],-1)
counts95['linkoflist'] = (counts95.loc[np.argmax(m, axis=1), 'index']
.where(m.any(1)).to_numpy())
Out[351]:
index level0 links linkoflist
0 616351 25 [1, 2, 3, 4, 5] NaN
1 616352 30 [23, 45, 2] 616351.0
2 616353 35 [1, 19, 67] 616351.0
3 6457754 100 [14, 15, 16] NaN
4 6566666 200 [1, 14] 616351.0
5 6457754 556 [14, 1] 616351.0
方法二:
如果您的数据框很大,将每个子列表与links
的顶部进行比较会使其更快。在大数据帧上,方法 1 的速度可能快 2 倍。
c95_list = counts95.links.tolist()
m = [[any(x in l2 for x in l1) for l2 in c95_list[:i]] for i,l1 in enumerate(c95_list)]
counts95['linkoflist'] = counts95.reindex([np.argmax(y) if any(y) else np.nan
for y in m])['index'].to_numpy()
循序渐进(方法一)
m = np.tril([[any(x in l2 for x in l1) for l2 in c95_list] for l1 in c95_list],-1)
Out[353]:
array([[False, False, False, False, False, False],
[ True, False, False, False, False, False],
[ True, False, False, False, False, False],
[False, False, False, False, False, False],
[ True, False, True, True, False, False],
[ True, False, True, True, True, False]])
argmax
返回第一个True
和第一个False
的所有-False
行的位置。
In [354]: np.argmax(m, axis=1)
Out[354]: array([0, 0, 0, 0, 0, 0], dtype=int64)
使用argmax
的结果进行切片
counts95.loc[np.argmax(m, axis=1), 'index']
Out[355]:
0 616351
0 616351
0 616351
0 616351
0 616351
0 616351
Name: index, dtype: int64
链where
将所有False
对应的行从m
转换为NaN
counts95.loc[np.argmax(m, axis=1), 'index'].where(m.any(1))
Out[356]:
0 NaN
0 616351.0
0 616351.0
0 NaN
0 616351.0
0 616351.0
Name: index, dtype: float64
最后,输出的索引与counts95
的索引不同,所以只需调用to_numpy
获取ndarray分配给counts95
的列linkoflist
。
【讨论】:
这使我的循环从 3 小时缩短到 6 分钟,总共 15000 行。我知道您在这里给出了很好的解释,您能否为此添加您的整体逻辑以更好地理解代码,我将非常感谢并将答案标记为正确。 @Rishi:我在答案中添加了详细逻辑。我希望它有帮助:)【参考方案4】:您可以更多地操作数据的另一种选择;
代码
import pandas as pd
counts95 = pd.DataFrame('index': [616351, 616352, 616353,6457754,6566666,464664683],
'level0': [25,30,35,100,200,556],
'links' : [[1,2,3,4,5],[23,45,2],[1,19,67],[14,15,16],[1,14],[14,1]],
'linksoflinks' : [None,None,None,None,None,None])
def has_match(ar1, ar2):
return bool(set(ar1).intersection(ar2))
def set_linksoflinks(df):
for i, row in df.iterrows():
j = i+1
while j<df.shape[0]:
check = has_match(row['links'], df.loc[j, 'links'])
if check and not df.loc[j, 'linksoflinks']:
df.loc[j, 'linksoflinks'] = row['index']
j+=1
return df.copy()
df = set_linksoflinks(counts95)
print(df)
输出
index level0 links linksoflinks
0 616351 25 [1, 2, 3, 4, 5] None
1 616352 30 [23, 45, 2] 616351
2 616353 35 [1, 19, 67] 616351
3 6457754 100 [14, 15, 16] None
4 6566666 200 [1, 14] 616351
5 464664683 556 [14, 1] 616351
【讨论】:
这不是预期的输出,因为第 4 行和第 5 行应该有 616351,这是我们只用一个值标记重复的地方 对不起,忘了把空检查:)。请参阅编辑后的答案。以上是关于将列表与 DataFrame 中的每条记录进行比较的主要内容,如果未能解决你的问题,请参考以下文章