使用 pandas 中的两列创建层次结构
create hierarchy using two columns in pandas
我正在处理的数据如下:
Name RefSecondary RefMain
test 2 3
bet 3 4
get 1 2
set null 1
net 3 5
我做了一个非常简单的查询,它在数据框中查找值的存在并构建层次结构
sys_role = 'sample.xlsx'
df = pd.read_excel(sys_role,na_filter = False).apply(lambda x: x.astype(str).str.strip())
for i in range(count):
for j in range(count):
if df.iloc[i]['RefMain'] == df.iloc[j]['RefSecondary']:
df.iloc[j, df.columns.get_loc('Name')] = "/".join([df.iloc[i]['Name'],df.iloc[j]['Name']])
j = j+1
i = i+1
我得到的结果如下:
Result RefMain
0 get/test 3
1 test/bet 4
2 set/get 2
3 set 1
4 test/net 5
这真的很慢,逻辑也不完美。有什么方法可以更快地完成这项工作吗?
逻辑需要如下:
1)Take a value from column RefMain,and find its correspoding RefSecondary value.
2)Look up the RefSecondary value in RefMain,
3)If found Back to Step 1 and repeat.
4)This continues recursively till no value/null is found in RefSecondary column.
结果数据框应如下所示:
Result RefMain
0 set/get/test 3
1 set/get/test/bet 4
2 set/get 2
3 set 1
4 set/get/test/net 5
以下代码查找 ref(在本例中为 1),直到找不到任何行。它输出
def lookup(df, ref):
arr_result=[]
result = []
row = df[df.RefMain==ref]
while len(row)>0:
arr_result.append(row.Name.iloc[0])
result.append(("/".join(arr_result), row.RefMain.iloc[0]))
row = df[df.RefSecondary == row.RefMain.iloc[0] ]
return pd.DataFrame(result, columns=["Result", "RefMain"])
lookup(df,1)
输出
Result RefMain
0 set 1
1 set/get 2
2 set/get/test 3
3 set/get/test/bet 4
在上面的问题中,你如何获得第 "set/get/test/net 5" 行,我是漏掉了什么还是弄错了?
此代码与 merges
一起工作。它有点扭曲,但它应该 运行 快,因为(也许是因为)没有行迭代。
简而言之,它会不断合并,直到所有新的 RefSecondary
值都为空。
我想它还可以进一步优化屏蔽 merge
操作。
df_ref = df.copy()
df.rename(columns={'Name':'Result'},inplace=True)
while not np.all(pd.isnull(df['RefSecondary'])):
df = df.merge(df_ref,how='left',
left_on='RefSecondary',right_on='RefMain',
suffixes=['_old',''])
mask_=pd.notnull(df['RefMain'])
df.loc[mask_,'Result'] = df.loc[mask_,'Result']+'/'+df.loc[mask_,'Name']
df.drop(['RefSecondary_old','RefMain_old','Name'],axis='columns',inplace=True)
df = df[['Result']].join(df_ref['RefMain'])
源数据:
df = pd.DataFrame(data=[['test',2,3],
['bet',3,4],
['get',1,2],
['set','null',1],
['net',3,5]],
columns=['Name','RefSecondary','RefMain'])
顺便说一句,这段代码假设原始数据是一致的。例如,如果链接中有一个循环,它将陷入无限循环。
这听起来像是一个图形问题。您可以尝试 networkx
如下:
df = df.fillna(-1)
# create a graph
G = nx.DiGraph()
# add reference as edges
G.add_edges_from(zip(df['RefMain'],df['RefSecondary'] ))
# rename the nodes accordingly
G = nx.relabel_nodes(G, mapping=df.set_index('RefMain')['Name'].to_dict())
# merge the path list to the dataframe
df = df.merge(pd.DataFrame(nx.shortest_path(G)).T['null'],
left_on='Name',
right_index=True)
# new column:
df['Path'] = df['null'].apply(lambda x: '/'.join(x[-2::-1]) )
输出:
Name RefSecondary RefMain null Path
0 test 2 3 [test, get, set, null] set/get/test
1 bet 3 4 [bet, test, get, set, null] set/get/test/bet
2 get 1 2 [get, set, null] set/get
3 set null 1 [set, null] set
4 net 3 5 [net, test, get, set, null] set/get/test/net
您可以将列 RefMain
设置为索引并使用方法 reindex()
:
访问字符串
# Convert 'RefSecondary' to numeric and set 'RefMain' as index
df['RefSecondary'] = pd.to_numeric(df.RefSecondary, errors='coerce')
df.set_index('RefMain', drop=False, inplace=True)
lst = [df['Name'].values]
new_df = df.copy()
# Iterate until all values in 'Name' are NaN
while new_df['Name'].notna().any():
new_df = df.reindex(new_df['RefSecondary'])
lst.append(new_df['Name'].values)
你得到以下数组列表lst
:
[array(['test', 'bet', 'get', 'set', 'net'], dtype=object),
array(['get', 'test', 'set', nan, 'test'], dtype=object),
array(['set', 'get', nan, nan, 'get'], dtype=object),
array([nan, 'set', nan, nan, 'set'], dtype=object),
array([nan, nan, nan, nan, nan], dtype=object)]
现在您可以连接字符串并创建新的 df。
result = ['/'.join(filter(np.nan.__eq__, i)) for i in zip(*lst[::-1])]
result = pd.DataFrame({'Result': result, 'RefMain': df['RefMain'].values})
最终结果:
Result RefMain
0 set/get/test 3
1 set/get/test/bet 4
2 set/get 2
3 set 1
4 set/get/test/net 5
我正在处理的数据如下:
Name RefSecondary RefMain
test 2 3
bet 3 4
get 1 2
set null 1
net 3 5
我做了一个非常简单的查询,它在数据框中查找值的存在并构建层次结构
sys_role = 'sample.xlsx'
df = pd.read_excel(sys_role,na_filter = False).apply(lambda x: x.astype(str).str.strip())
for i in range(count):
for j in range(count):
if df.iloc[i]['RefMain'] == df.iloc[j]['RefSecondary']:
df.iloc[j, df.columns.get_loc('Name')] = "/".join([df.iloc[i]['Name'],df.iloc[j]['Name']])
j = j+1
i = i+1
我得到的结果如下:
Result RefMain
0 get/test 3
1 test/bet 4
2 set/get 2
3 set 1
4 test/net 5
这真的很慢,逻辑也不完美。有什么方法可以更快地完成这项工作吗?
逻辑需要如下:
1)Take a value from column RefMain,and find its correspoding RefSecondary value. 2)Look up the RefSecondary value in RefMain, 3)If found Back to Step 1 and repeat. 4)This continues recursively till no value/null is found in RefSecondary column.
结果数据框应如下所示:
Result RefMain
0 set/get/test 3
1 set/get/test/bet 4
2 set/get 2
3 set 1
4 set/get/test/net 5
以下代码查找 ref(在本例中为 1),直到找不到任何行。它输出
def lookup(df, ref):
arr_result=[]
result = []
row = df[df.RefMain==ref]
while len(row)>0:
arr_result.append(row.Name.iloc[0])
result.append(("/".join(arr_result), row.RefMain.iloc[0]))
row = df[df.RefSecondary == row.RefMain.iloc[0] ]
return pd.DataFrame(result, columns=["Result", "RefMain"])
lookup(df,1)
输出
Result RefMain
0 set 1
1 set/get 2
2 set/get/test 3
3 set/get/test/bet 4
在上面的问题中,你如何获得第 "set/get/test/net 5" 行,我是漏掉了什么还是弄错了?
此代码与 merges
一起工作。它有点扭曲,但它应该 运行 快,因为(也许是因为)没有行迭代。
简而言之,它会不断合并,直到所有新的 RefSecondary
值都为空。
我想它还可以进一步优化屏蔽 merge
操作。
df_ref = df.copy()
df.rename(columns={'Name':'Result'},inplace=True)
while not np.all(pd.isnull(df['RefSecondary'])):
df = df.merge(df_ref,how='left',
left_on='RefSecondary',right_on='RefMain',
suffixes=['_old',''])
mask_=pd.notnull(df['RefMain'])
df.loc[mask_,'Result'] = df.loc[mask_,'Result']+'/'+df.loc[mask_,'Name']
df.drop(['RefSecondary_old','RefMain_old','Name'],axis='columns',inplace=True)
df = df[['Result']].join(df_ref['RefMain'])
源数据:
df = pd.DataFrame(data=[['test',2,3],
['bet',3,4],
['get',1,2],
['set','null',1],
['net',3,5]],
columns=['Name','RefSecondary','RefMain'])
顺便说一句,这段代码假设原始数据是一致的。例如,如果链接中有一个循环,它将陷入无限循环。
这听起来像是一个图形问题。您可以尝试 networkx
如下:
df = df.fillna(-1)
# create a graph
G = nx.DiGraph()
# add reference as edges
G.add_edges_from(zip(df['RefMain'],df['RefSecondary'] ))
# rename the nodes accordingly
G = nx.relabel_nodes(G, mapping=df.set_index('RefMain')['Name'].to_dict())
# merge the path list to the dataframe
df = df.merge(pd.DataFrame(nx.shortest_path(G)).T['null'],
left_on='Name',
right_index=True)
# new column:
df['Path'] = df['null'].apply(lambda x: '/'.join(x[-2::-1]) )
输出:
Name RefSecondary RefMain null Path
0 test 2 3 [test, get, set, null] set/get/test
1 bet 3 4 [bet, test, get, set, null] set/get/test/bet
2 get 1 2 [get, set, null] set/get
3 set null 1 [set, null] set
4 net 3 5 [net, test, get, set, null] set/get/test/net
您可以将列 RefMain
设置为索引并使用方法 reindex()
:
# Convert 'RefSecondary' to numeric and set 'RefMain' as index
df['RefSecondary'] = pd.to_numeric(df.RefSecondary, errors='coerce')
df.set_index('RefMain', drop=False, inplace=True)
lst = [df['Name'].values]
new_df = df.copy()
# Iterate until all values in 'Name' are NaN
while new_df['Name'].notna().any():
new_df = df.reindex(new_df['RefSecondary'])
lst.append(new_df['Name'].values)
你得到以下数组列表lst
:
[array(['test', 'bet', 'get', 'set', 'net'], dtype=object),
array(['get', 'test', 'set', nan, 'test'], dtype=object),
array(['set', 'get', nan, nan, 'get'], dtype=object),
array([nan, 'set', nan, nan, 'set'], dtype=object),
array([nan, nan, nan, nan, nan], dtype=object)]
现在您可以连接字符串并创建新的 df。
result = ['/'.join(filter(np.nan.__eq__, i)) for i in zip(*lst[::-1])]
result = pd.DataFrame({'Result': result, 'RefMain': df['RefMain'].values})
最终结果:
Result RefMain
0 set/get/test 3
1 set/get/test/bet 4
2 set/get 2
3 set 1
4 set/get/test/net 5