Легко генерировать список ребер из заданной c структуры, используя pandas - PullRequest
3 голосов
/ 19 марта 2020

Это вопрос о том, как все сделать правильно с pandas (я использую версию 1.0). Допустим, у меня есть DataFrame с миссиями, который содержит источник и одно или несколько мест назначения:

   mid from         to
0    0    A        [C]
1    1    A     [B, C]
2    2    B        [B]
3    3    C  [D, E, F]

Например: для миссии (mid=1) люди будут путешествовать от A до B, затем от B до C и, наконец, от C до A. Обратите внимание, что я не контролирую модель данных входного фрейма данных.

Я хотел бы вычислять метрики для каждой поездки миссии. Ожидаемый результат будет точно:

    tid  mid from to
0     0    0    A  C
1     1    0    C  A
2     2    1    A  B
3     3    1    B  C
4     4    1    C  A
5     5    2    B  B
6     6    2    B  B
7     7    3    C  D
8     8    3    D  E
9     9    3    E  F
10   10    3    F  C

Я нашел способ достичь своей цели. Пожалуйста, найдите ниже MCVE:

import pandas as pd

# Input:
df = pd.DataFrame(
    [["A", ["C"]],
     ["A", ["B", "C"]],
     ["B", ["B"]],
     ["C", ["D", "E", "F"]]],
    columns = ["from", "to"]
).reset_index().rename(columns={'index': 'mid'})

# Create chain:
df['chain'] = df.apply(lambda x: list(x['from']) + x['to'] + list(x['from']), axis=1)
# Explode chain:
df = df.explode('chain')
# Shift to create travel:
df['end'] = df.groupby("mid")["chain"].shift(-1)
# Remove extra row, clean, reindex and rename:
df = df.dropna(subset=['end']).reset_index(drop=True).reset_index().rename(columns={'index': 'tid'})
df = df.drop(['from', 'to'], axis=1).rename(columns={'chain': 'from', 'end': 'to'})

Мой вопрос: Есть ли лучший / более простой способ сделать это с Pandas? Говоря лучше, я имею в виду, не нужно больше производительный (это может быть конечно), но более читаемый и интуитивно понятный.

Ответы [ 2 ]

3 голосов
/ 19 марта 2020

Ваша операция в основном explode и concat:

# turn series of lists in to single series
tmp = df[['mid','to']].explode('to')

# new `from` is concatenation of `from` and the list
df1 = pd.concat((df[['mid','from']],
                 tmp.rename(columns={'to':'from'})
          )
         ).sort_index()

# new `to` is concatenation of list and `to``
df2 = pd.concat((tmp,
                 df[['mid','from']].rename(columns={'from':'to'})
                )
         ).sort_index()

df1['to'] = df2['to']

Вывод:

   mid from to
0    0    A  C
0    0    C  A
1    1    A  B
1    1    B  C
1    1    C  A
2    2    B  B
2    2    B  B
3    3    C  D
3    3    D  E
3    3    E  F
3    3    F  C
2 голосов
/ 19 марта 2020

Если вы не против перестроить весь DataFrame, вы можете немного его очистить с помощью np.roll, чтобы получить пары адресатов, а затем присвоить значение mid в зависимости от количества поездок (длина каждого подсписка в l)

import pandas as pd
import numpy as np
from itertools import chain

l = [[fr]+to for fr,to in zip(df['from'], df['to'])]

df1 = (pd.DataFrame(data=chain.from_iterable([zip(sl, np.roll(sl, -1)) for sl in l]),
                    columns=['from', 'to'])
         .assign(mid=np.repeat(df['mid'].to_numpy(), [*map(len, l)])))

   from to  mid
0     A  C    0
1     C  A    0
2     A  B    1
3     B  C    1
4     C  A    1
5     B  B    2
6     B  B    2
7     C  D    3
8     D  E    3
9     E  F    3
10    F  C    3
...