【问题标题】:Python Merge Two Numpy Arrays Based on ConditionPython根据条件合并两个Numpy数组
【发布时间】:2019-05-13 09:08:55
【问题描述】:

如何通过在数组 B 中查找数组 A 中的值来合并以下两个数组?

数组 A:

array([['GG', 'AB', IPv4Network('1.2.3.41/26')],
       ['GG', 'AC', IPv4Network('1.2.3.42/25')],
       ['GG', 'AD', IPv4Network('1.2.3.43/24')],
       ['GG', 'AE', IPv4Network('1.2.3.47/23')],
       ['GG', 'AF', IPv4Network('1.2.3.5/24')]],
      dtype=object)

和数组 B:

array([['123456', 'A1', IPv4Address('1.2.3.5'), nan],
       ['987654', 'B1', IPv4Address('1.2.3.47'), nan]],
      dtype=object)  

这里的目标是创建 Array C,通过在 Array A 中查找 Array B 中的 IPv4Address 并比较它们,得到对应数组的第二个值并存储它:

数组 C:

array([['123456', 'A1', IPv4Address('1.2.3.5'), nan, 'AF'],
       ['987654', 'B1', IPv4Address('1.2.3.47'), nan, 'AE']],
      dtype=object) 

IP 地址属于这种类型:https://docs.python.org/3/library/ipaddress.html#ipaddress.ip_network

我怎样才能做到这一点?

编辑:

请注意,合并以 IP 匹配为条件,因此结果数组 C 将具有与数组 B 相同数量的数组,但它会多一个值。建议的重复链接没有回答相同的问题。

【问题讨论】:

  • 如果你有 Pandas,为什么要用 NumPy 做这个?它不会真的更快。
  • 我在尝试创建您的 A 数组时收到此错误:ValueError: 1.2.3.41/26 has host bits set
  • @huynhsamha 这是虚拟数据,我没有输入真实的IP地址,也许这就是原因
  • 那么请努力确保数据有效,以便至少可以复制粘贴并运行。
  • 您需要的唯一答案是 object-dtype numpy 数组毫无意义,它们没有为您提供我们首先使用 numpy 的速度或内存优势。只需使用一个列表并在本机 python 中完成它。

标签: python arrays numpy vectorization


【解决方案1】:

这应该可以满足您的要求(至少输出正是您想要的),我做了一些小假设来处理您的#dummydata,但这并不重要。

代码:

import numpy as np
import ipaddress as ip

array_A = np.array([['GG', 'AB', ip.ip_network('192.168.0.0/32')],
                    ['GG', 'AC', ip.ip_network('192.168.0.0/31')],
                    ['GG', 'AD', ip.ip_network('192.168.0.0/30')],
                    ['GG', 'AE', ip.ip_network('192.168.0.0/29')],
                    ['GG', 'AF', ip.ip_network('192.168.0.0/28')]],
                   dtype=object)

array_B = np.array([['123456', 'A1', ip.ip_network('192.168.0.0/28'), np.nan],
                    ['987654', 'B1', ip.ip_network('192.168.0.0/29'), np.nan]],
                   dtype=object)


def merge_by_ip(A, B):
    # initializing an empty array with len(B) rows and 5 columns for the values you want to save in it
    C = np.empty([len(B), 5],dtype=object)
    for n in range(len(B)):
        for a in A:
            # checking condition: if ip address in a is ip address in b
            if a[2] == B[n][2]:
                # add the entry of b with the second value of a to the new Array c
                C[n] = np.append(B[n], a[1])
    return C


print(merge_by_ip(array_A, array_B))

输出:

[['123456' 'A1' IPv4Network('192.168.0.0/28') nan 'AF']
 ['987654' 'B1' IPv4Network('192.168.0.0/29') nan 'AE']]

注意:

此解决方案具有O(m * n) 复杂性,这不是必需的,有许多开箱即用的 (Pandas) 和自定义(例如使用 dict)方法以较低的复杂性进行合并。

【讨论】:

  • 您能评论一些值吗?例如:np.empty([len(B), 5] 中的 5 是多少,a[2]B[n][2]a[1] 中的 3 是多少?
  • 我在代码中添加了它。我不明白你说的 3 是什么意思。
【解决方案2】:

您似乎没有理由不能使用 Pandas。如果您的 IP 地址完全对齐,您可以 merge 然后使用 pd.DataFrame.values 返回一个 NumPy 数组:

import pandas as pd

# data from @mk18
df_A = pd.DataFrame(array_A[:, 1:], columns=['', 'IP'])
df_B = pd.DataFrame(array_B, columns=['id', 'value', 'IP', 'na'])

res = df_B.merge(df_A, on='IP').values

print(res)

array([['123456', 'A1', IPv4Network('192.168.0.0/28'), nan, 'AF'],
       ['987654', 'B1', IPv4Network('192.168.0.0/29'), nan, 'AE']],
      dtype=object)

如果您希望在合并时忽略网络组件并仅包含network_address,即使用'1.2.3.5' 而不是'1.2.3.5/24',那么您可以在合并前创建辅助系列:

import pandas as pd
from operator import attrgetter

df_A = pd.DataFrame(array_A[:, 1:], columns=['key', 'IP'])
df_B = pd.DataFrame(array_B, columns=['id', 'value', 'IP', 'na'])

df_A['IP_NoNetwork'] = df_A['IP'].map(attrgetter('network_address'))
df_B['IP_NoNetwork'] = df_B['IP'].map(attrgetter('network_address'))

res = df_B.merge(df_A.drop('IP', 1), on='IP_NoNetwork')\
          .loc[:, ['id', 'value', 'IP', 'na', 'key']].values

【讨论】:

    【解决方案3】:

    您的数据存在问题和复杂性,导致您无法使用 join_byrec_join 作为您链接的建议问题。

    正如其他人所指出的,您的数据的主要问题是 IPv4Network('1.2.3.4/24') 这样的网络不是有效的网络,因为它们设置了被 /24 屏蔽的主机位。 /24 表示最后的 32 - 24 = 8 位是您的主机位,IPv4Network 的构造函数要求将这些设置为 0,例如 IPv4Network('1.2.3.0/24') 是有效的。

    主要的复杂性是您在一个数组中有网络,而在另一个数组中有地址。像rec_joinjoin_by 这样的方法使用比较(即==)来决定哪些记录放在一起。其他一些建议的答案通过用地址替换您的网络来“解决”这个问题,但这似乎不是您的问题。

    另外,请注意,一个网络地址可能属于多个不同的网络。例如,IPv4Address('1.2.3.129') 属于IPv4Network('1.2.3.0/24')IPv4Network('1.2.3.128/25')。因此,我假设您希望这两个匹配项都会显示在您的结果中。

    要将一个数组中的地址连接到地址实际所在的网络,您必须自己遍历数组并构造一个新数组。有效的比较类型是IPv4Address('1.2.3.129') in IPv4Network('1.2.3.0/24')(这是True)。

    将这些结合在一起的工作代码示例:

    from numpy import nan, asarray, concatenate
    from ipaddress import IPv4Address, IPv4Network
    
    a = asarray([
        ['GG', 'AA', IPv4Network('1.2.4.0/24')],
        ['GG', 'AB', IPv4Network('1.2.3.128/25')],
        ['GG', 'AC', IPv4Network('1.2.3.0/24')]
    ], dtype=object)
    
    b = asarray([
        ['123456', 'A1', IPv4Address('1.2.3.4'), nan],
        ['987654', 'B1', IPv4Address('1.2.3.129'), nan],
        ['024680', 'C1', IPv4Address('1.2.4.0'), nan]
    ], dtype=object)
    
    
    def join_addresses_networks(addresses, networks):
        for address in addresses:
            for network in networks:
                if address[2] in network[2]:
                    yield concatenate((address, network[:-1]))
    
    
    c = asarray(list(join_addresses_networks(b, a)))
    
    print(c)
    

    【讨论】:

    • 这个解决方案有 O(m * n) 复杂度,这里没有必要,有很多开箱即用(Pandas)和自定义(例如使用dict)的合并方式复杂度较低。
    • 你是对的@jpp,但我认为一个直接的答案是更好的答案,因为问题不是关于性能,而是关于基本的“如何”。为了提高效率,答案可以修改为 O(m + n),留给读者作为练习。
    猜你喜欢
    • 1970-01-01
    • 2021-09-17
    • 1970-01-01
    • 1970-01-01
    • 2020-10-13
    • 2020-12-21
    • 1970-01-01
    • 2017-09-19
    • 1970-01-01
    相关资源
    最近更新 更多