Создать новые столбцы на основе древовидного шаблона - PullRequest
3 голосов
/ 04 апреля 2020

У меня есть следующий фрейм данных:

col1      col2               
basic      c                   
c          c++                 
c++        java                
ruby                                     
php                                      
java       python              
python                                   
r                                        
c#                                      

Я хочу создать новые столбцы на основе шаблона, указанного в фрейме данных.
Например, в приведенном выше фрейме данных можно наблюдать порядок basic->c->c++->java->python из col1 и col2.

Logi c:

значение col1 basic имеет значение c в col2, аналогично c значение в col1 соответствует c++ в столбце 2, c++ приводит к java в col2 и, наконец, java до python в col2.
Остальные значения в "col1", которые имеют соответствующие пробелы в col2 также будет оставлено пустым во вновь создаваемых столбцах. (то есть мы рассматриваем только те значения в «col1», которые не имеют пробелов в col2).

Итак, мой выходной фрейм данных будет:

     col1     col2   new_col1  new_col2   new_col3   new_col4   
0   basic       c          c        c++       java     python
1       c     c++        c++       java     python         
2     c++    java       java     python                  
3    ruby                                              
4     php                                              
5    java  python     python                           
6  python                                             
7       r                                               
8       c               

Спасибо!

1 Ответ

2 голосов
/ 04 апреля 2020

Это можно решить с помощью теории графов анализа. Похоже, вы хотите получить все преемников , начиная с каждого из узлов в col2. Для этого нам нужно сначала построить ориентированный граф , используя столбцы col1 и col2. Для этого мы можем использовать networkX и построить nx.DiGraph из кадра данных, используя nx.from_pandas_edgelist:

import networkx as nx

m = df.ne('').all(1)
G = nx.from_pandas_edgelist(df[m], 
                            source='col1', 
                            target='col2', 
                            create_using=nx.DiGraph())

И затем мы можем перебрать nodes в col2 и поиск всех преемников, начиная с этого узла. Для этого мы можем использовать dfs_tree, который будет проходить по графику в поисках преемников с первым поиском по глубине из источника:

all_successors = [list(nx.dfs_tree(G, node)) for node in df.loc[m,'col2']]

Теперь мы можем присвоить обратно список самых длинных путей с:

out = (df.assign(
         **pd.DataFrame(all_successors, index=df[m].index)
         .reindex(df.index)
         .fillna('')
         .add_prefix('new_col')))

print(out)

     col1    col2   new_col0   new_col1   new_col2   new_col3
0   basic       c          c        c++       java     python
1       c     c++        c++       java     python         
2     c++    java       java     python                  
3    ruby                                              
4     php                                              
5    java  python     python                           
6  python                                             
7       r                                               
8       c                  

Чтобы лучше объяснить этот подход, рассмотрим вместо этого слегка отличную сеть с дополнительным компонентом:

enter image description here

Как уже упоминалось, здесь мы хотим получить список преемников для каждого из узлов, которые у нас есть в Col2. Для этих задач существует несколько алгоритмов поиска графа, которые можно использовать для изучения ветвей графа, начиная с данного узла. Для этого мы можем использовать функции depth first search, доступные в nx.algorithms.traversal. В этом случае нам нужен nx.dfs_tree, который возвращает ориентированное дерево , построенное посредством поиска в глубину, начиная с указанного узла.

Вот несколько примеров :

list(nx.dfs_tree(G, 'c++'))
# ['c++', 'java', 'python', 'julia']

list(nx.dfs_tree(G, 'python'))
# ['python', 'julia']

list(nx.dfs_tree(G, 'basic'))
# ['basic', 'php', 'sql']

Обратите внимание, что в случае наличия циклов внутри графика это может оказаться довольно сложным. Скажем, есть, например, грань между c++ и scala. В этом случае становится неясно, какой путь выбрать. Одним из способов может быть обход всех соответствующих путей с помощью nx.dfs_tree и сохранение одного интересующего пути, предопределяющего некоторые логики c, например, сохранение самого длинного. Хотя, похоже, дело не в этой проблеме.

...