Новые столбцы в Dataframe с данными из другого DataFrame

Есть датафрейм вида

Var    analog     Кол-во 
s1       s2          6
NaN      s3         NaN
NaN      s4         NaN
NaN      s5         NaN  
NaN      NaN        NaN  
r1       NaN         4  
l1       l2          1
NaN      l3         NaN 

Этот датафрейм описывает комплектующие и их аналоги. Например, s1 имеет аналоги s2, s3, s4 и s5, всего их необходимо 6 шт. r1 не имеет аналогов и этого компонента нужно 4 шт. l1 имеет аналоги l2 и l3 и таких компонентов нужен 1 шт.

Необходимо преобразовать данный датафрейм в новый датафрейм с числом столбцов, равным максимальному количеству однотипных значений столбца analog во всем датафрейме плюс два столбца, а новые столбцы должны быть заполнены значениями этих аналогов. Пустые строки должны быть удалены. Ожидаемый результат:

Var    analog1     analog2    analog3   analog4   Кол-во 
s1       s2          s3          s4        s5       6 
r1       NaN         NaN         NaN       NaN      4  
l1       l2          l3          NaN       NaN      1

Приходят идеи обрабатывать всё это в цикле и if-ами анализировать содержимое каждой новой строки, сравнивая с предыдущей, но можно ли это сделать без циклов?


Ответы (1 шт):

Автор решения: Vitalizzare ушел в монастырь

Будем исходить из того, что строки упорядочены как представлено в примере:

  • все аналоги идут подряд;
  • название и количество указаны в первой строке непрерывной группы;
  • для каждой группы название и количество не Nan.

В таком контексте вам нужно сгруппировать аналоги в список и преобразовать результат в pandas.Series в методе apply. Например:

import pandas as pd

df = pd.DataFrame({'Var': ['s1', None, None, None, None, 'r1', 'l1', None],
 'analog': ['s2', 's3', 's4', 's5', None, None, 'l2', 'l3'],
 'Кол-во': [6.0, None, None, None, None, 4.0, 1.0, None]})

df = df.dropna(how='all')       # сбрасываем пустые строки
grouper = df['Var'].ffill()     # заменяем пустые `Var` ближайшим сверху непустым значением

analogs = (
    df.groupby(grouper)['analog']
    .agg(list)
    .apply(pd.Series)
    .rename(columns=lambda x: f'analog{x+1}')
)
counts = df[['Var', 'Кол-во']].dropna().set_index('Var')
answer = analogs.join(counts).reset_index()

print(answer)
  Var analog1 analog2 analog3 analog4  Кол-во
0  l1      l2      l3     NaN     NaN     1.0
1  r1     NaN     NaN     NaN     NaN     4.0
2  s1      s2      s3      s4      s5     6.0

Мы могли бы передать pandas.Series в метод SeriesGroupBy.apply сгруппированных значений. Но этот метод отличается от такого же по названию, но другого по реализации метода Series.apply. А именно, он сохранит исходные индексы аналогов и разместит их вторым уровнем (на первом уровне - значения Var). Если идти по этому пути, то нужно передать функцию, которая в группе аналогов вытрет индексы исходной таблицы. После этого очищенный индекс, нужно развернуть в столбцы методом unstack:

(
    df.groupby(grouper)['analog']
    .apply(lambda series: series.reset_index(drop=True))
    .unstack()
)
       0    1    2    3
Var                    
l1    l2   l3  NaN  NaN
r1   NaN  NaN  NaN  NaN
s1    s2   s3   s4   s5
→ Ссылка