wake-up-neo.net

Python - Zusammenführen von zwei numpy Arrays basierend auf den Bedingungen

Wie kann ich die folgenden zwei Arrays zusammenführen, indem Sie einen Wert von Array A in Array B nachschlagen?

Array 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)

und Array B:

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

Das Ziel ist hier, Array C zu erstellen, indem die IPv4Address von Array B in Array A nachgeschlagen und verglichen wird und der zweite Wert des entsprechenden Arrays abgerufen und gespeichert wird:

Array C: 

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

Die IP-Adressen sind von diesem Typ: https://docs.python.org/3/library/ipaddress.html#ipaddress.ip_network

Wie kann ich das erreichen?

bearbeiten:

Bitte beachten Sie, dass das Zusammenführen von der Übereinstimmung der IPs abhängig ist, so dass das resultierende Array C die gleiche Anzahl von Arrays wie das Array B hat, aber einen weiteren Wert. Die vorgeschlagenen doppelten Links beantworten nicht dieselbe Frage.

8
storis

Dies sollte das tun, wonach Sie gefragt haben (zumindest ist die Ausgabe genau das, was Sie wollten). Ich habe einige kleinere Annahmen gemacht, um mit Ihren #dummydata umzugehen, aber das sollte keine große Rolle spielen.

Code:

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))

Ausgabe:

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

Hinweis:

Diese Lösung hat eine O(m * n)-Komplexität, die nicht unbedingt erforderlich ist. Es gibt viele out-of-the-box (Pandas) und benutzerdefinierte (z. B. mit dict) Methoden, mit geringerer Komplexität zusammenzuführen.

6
mrk

Es scheint keinen Grund zu geben, warum Sie Pandas kann nicht verwenden. Wenn Ihre IP-Adressen perfekt übereinstimmen, können Sie merge und dann pd.DataFrame.values um ein NumPy Array zurückzugeben:

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)

Wenn Sie die Netzwerkkomponente ignorieren und nur das network_address beim Zusammenführen, d. h. benutze '1.2.3.5' anstatt '1.2.3.5/24', dann können Sie vor dem Zusammenführen eine Hilfsserie erstellen:

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
jpp

Es gibt Probleme mit Ihren Daten und Komplikationen, die Sie daran hindern, join_by oder rec_join als die von Ihnen verlinkte Frage zu verwenden.

Das Hauptproblem bei Ihren Daten ist, wie andere darauf hingewiesen haben, dass Netzwerke wie IPv4Network('1.2.3.4/24') keine gültigen Netzwerke sind, da sie über gesetzte Host-Bits verfügen, die durch den /24 ausgeblendet sind. Der /24 bedeutet, dass die letzten 32 - 24 = 8-Bits die Hostbits sind und der Konstruktor für IPv4Network erfordert, dass diese auf 0 gesetzt sind. Beispielsweise ist IPv4Network('1.2.3.0/24') gültig.

Die Hauptkomplikation ist, dass Sie Netzwerke in einem Array, aber Adressen im anderen haben. Methoden wie rec_join und join_by verwenden Vergleich (d. H. ==), um zu entscheiden, welche Datensätze zusammenpassen. Einige der anderen vorgeschlagenen Antworten lösen dieses Problem, indem Sie Ihre Netzwerke durch Adressen ersetzen. Dies scheint jedoch nicht Ihr Problem zu sein.

Beachten Sie außerdem, dass eine einzelne Netzwerkadresse in mehrere verschiedene Netzwerke fallen kann. Zum Beispiel fällt IPv4Address('1.2.3.129') sowohl in IPv4Network('1.2.3.0/24') als auch in IPv4Network('1.2.3.128/25'). Ich gehe also davon aus, dass Sie davon ausgehen, dass beide Treffer in Ihren Ergebnissen erscheinen.

Um Adressen von einem Array mit Netzwerken zu verknüpfen, in die sich die Adresse tatsächlich befindet, müssen Sie das Array selbst durchlaufen und ein neues erstellen. Die Art des Vergleichs ist IPv4Address('1.2.3.129') in IPv4Network('1.2.3.0/24') (dies ist True).

Ein Beispiel für einen Arbeitscode, der dies zusammenbringt:

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)
0
Grismar