假设我有一个将子级地址映射到更宏观级地址的数据框:
Child | Child's Level | Parent | Parent's Level |
---|---|---|---|
Pivet Drive | Street | Little Whinging | Town |
Little Whinging | Town | England | Country |
England | Country | Europe | Continent |
State Street | Street | New York | City |
New York | City | USA | Country |
USA | Country | North America | Continent |
我有第二个数据框,列出了每个人的地址,但该地址可能会在不同的层级中说明
Name | Address | Continent? |
---|---|---|
Adam | Pivet Drive | |
Mary | New York | |
Dave | State Street |
如何使用 python 填充第二个数据框中的大陆列?
一种天真的方法是将第一个数据帧转换为字典并重复向上映射,或者只是重复合并两个数据帧。但是,一旦两个数据帧都有数百万行,这并不能很好地扩展,特别是因为每条记录都不是从层次结构中的同一级别开始的。
我以前使用图形数据库 (Neo4j) 填充了大陆列,但我似乎无法在谷歌上找到任何关于如何使用 python 来执行此操作的提示。
回答1
Graph DB就是为处理这种情况而生的,如果你想在relational-db/dataframe下处理(它们是一样的),你就无法避免有很多外连接的查询。这里隐藏的概念是如何在关系数据库中 store 和检索树状数据结构。您可以将数据框视为 db 中的 table 。
这里我使用Union-Find算法来处理这个问题,注意我没有使用除Continet之外的其他级别信息,如果两个Continets包含不同级别或同一级别下的同名地点,这可能是一个错误.以下代码只是一些演示想法,但它适用于您提供的演示数据,可能不适用于您的整个数据集:
import pandas as pd
from collections import defaultdict
df = pd.DataFrame({'Child': ['Pivet Drive', 'Little Whinging', 'England', 'State Street', 'New York', 'USA'],
"ChildLevel": ['Street', 'Town', 'Country', 'Street', 'City', 'Country'],
"Parent": ['Little Whinging', 'England', 'Europe', 'New York', 'USA', 'North America'],
"ParentLevel": ['Town', 'Country', 'Continent', 'City', 'Country', 'Continent']})
df_to_fill = pd.DataFrame({
'Name': ['Adam', 'Mary', 'Dave'],
'Address': ['Pivet Drive', 'New York', 'State Street'],
})
child_parent_value_pairs = df[["Child", "Parent"]].values.tolist()
tree = lambda: defaultdict(tree)
G = tree()
for child, parent in child_parent_value_pairs:
G[child][parent] = 1
G[parent][child] = 1
E = [(G[u][v], u, v) for u in G for v in G[u]]
T = set()
C = {u: u for u in G} # C stands for components
R = {u: 0 for u in G}
def find(C, u):
if C[u] != u:
C[u] = find(C, C[u]) # Path compression
return C[u]
def union(C, R, u, v):
u = find(C, u)
v = find(C, v)
if R[u] > R[v]:
C[v] = u
else:
C[u] = v
if R[u] == R[v]:
R[v] += 1
for __, u, v in sorted(E):
if find(C, u) != find(C, v):
T.add((u, v))
union(C, R, u, v)
all_continents = set(df[df['ParentLevel'] == 'Continent']['Parent'].tolist())
continent_lookup = {find(C, continent): continent for continent in all_continents}
df_to_fill['Continent'] = df_to_fill['Address'].apply(lambda x: continent_lookup.get(find(C, x), None))
print(df_to_fill)
输出:
Name Address Continent
0 Adam Pivet Drive Europe
1 Mary New York North America
2 Dave State Street North America