Python根据条件合并两个Numpy数组

时间:2018-12-12 09:37:10

标签: python arrays numpy vectorization

如何通过从数组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)  

这里的目标是创建数组C,方法是从数组A中的数组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地址的类型为:ServiceHostFactory

我该如何实现?

编辑:

请注意,合并的条件是IP匹配,因此生成的数组C的数组数与数组B的数组数相同,但其值更大。建议的重复链接不能回答相同的问题。

3 个答案:

答案 0 :(得分:6)

这应该可以满足您的要求(至少输出正是您想要的),我做了一些小的假设来处理您的#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)合并的方式较低的复杂性。

答案 1 :(得分:2)

您似乎没有理由不能使用熊猫。如果您的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

答案 2 :(得分:0)

您的数据存在一些问题,并且复杂性使您无法按照所链接的问题使用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)