将列表与 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】:

使用explodeduplicated.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 只是在一个项目的中间,但会做,它是否适用于您的解决方案?现在只需逐行打印以查看发生了什么并阅读explodemap 的文档,我会尽快回复您 感谢您的帮助,但有一个问题,如果存在具有两个不同索引的重复列表,则会出现错误:无法从重复轴重新索引,我正在编辑问题以再添加 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 被视为1False 被视为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 的位置,并链接wherem 的所有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 中的每条记录进行比较的主要内容,如果未能解决你的问题,请参考以下文章

为嵌套列表中的每条记录绘制回归线

在 Pandas 中为 DataFrame 中的每一行返回多行

根据查询中的每条记录将报告打印为 PDF

将 Dataframe 列的值与列表值进行比较

如何从左表中仅获取一条记录与右表中的每条记录

如何将每条记录与另一条记录进行比较(名称反转问题)并删除重复记录?