У меня есть два фрейма данных
Df1
ID Year Primary_Location Secondary_Location Sales
0 11 2023 NewYork Chicago 100
1 11 2023 Lyon Chicago,Paris 200
2 11 2023 Berlin Paris 300
3 12 2022 Newyork Chicago 150
4 12 2022 Lyon Chicago,Paris 250
5 12 2022 Berlin Paris 400
Df2
ID Year Primary_Location Sales
0 11 2023 Chicago 150
1 11 2023 Paris 200
2 12 2022 Chicago 300
3 12 2022 Paris 350
Я хотел бы, чтобы в каждой группе были одинаковые ID и Year: чтобы добавить столбец Sales от df2 до Sales в df1, где Primary_Location в df2 появляются (содержатся) в Secondary_Location в df1.
Например: для ID=11 и Year=2023, Sales для Lyon будет добавлено к Sales для Chicago и Sales для Paris из df_2.
Новое Sales из Paris для этой строки будет 200+150+200=550.
Ожидаемый результат будет:
df_primary_output
ID Year Primary_Location Secondary_Location Sales
0 11 2023 NewYork Chicago 250
1 11 2023 Lyon Chicago,Paris 550
2 11 2023 Berlin Paris 500
3 12 2022 Newyork Chicago 400
4 12 2022 Lyon Chicago,Paris 900
5 12 2022 Berlin Paris 750
Вот кадры данных для начала:
import pandas as pd
df1 = pd.DataFrame({'ID': [11, 11, 11, 12, 12, 12],
'Year': [2023, 2023, 2023, 2022, 2022, 2022],
'Primary_Location': ['NewYork', 'Lyon', 'Berlin', 'Newyork', 'Lyon', 'Berlin'],
'Secondary_Location': ['Chicago', 'Chicago,Paris', 'Paris', 'Chicago', 'Chicago,Paris', 'Paris'],
'Sales': [100, 200, 300, 150, 250, 400]
})
df2 = pd.DataFrame({'ID': [11, 11, 12, 12],
'Year': [2023, 2023, 2022, 2022],
'Primary_Location': ['Chicago', 'Paris', 'Chicago', 'Paris'],
'Sales': [150, 200, 300, 350]
})
Обновлено: pandas.errors.InvalidIndexError: переиндексация действительна только с объектами Index с уникальным значением
Было бы здорово, если бы решение могло работать и для этих входов:
Df1
Day ID Year Primary_Location Secondary_Location Sales
0 1 11 2023 NewYork Chicago 100
1 1 11 2023 Berlin Chicago 300
2 1 11 2022 Newyork Chicago 150
3 1 11 2022 Berlin Chicago 400
Df2
Day ID Year Primary_Location Sales
0 1 11 2023 Chicago 150
1 1 11 2022 Chicago 300
Ожидаемый результат будет:
df_primary_output
Day ID Year Primary_Location Secondary_Location Sales
0 1 11 2023 NewYork Chicago 250
1 1 11 2023 Berlin Chicago 450
2 1 11 2022 Newyork Chicago 450
3 1 11 2022 Berlin Chicago 700
Не так уж и прост твой вопрос...
Предлагаемый сценарий
import pandas as pd
df1 = pd.DataFrame({'ID': [11, 11, 11, 12, 12, 12],
'Year': [2023, 2023, 2023, 2022, 2022, 2022],
'Primary_Location': ['NewYork', 'Lyon', 'Berlin', 'Newyork', 'Lyon', 'Berlin'],
'Secondary_Location': ['Chicago', 'Chicago,Paris', 'Paris', 'Chicago', 'Chicago,Paris', 'Paris'],
'Sales': [100, 200, 300, 150, 250, 400]
})
df2 = pd.DataFrame({'ID': [11, 11, 12, 12],
'Year': [2023, 2023, 2022, 2022],
'Primary_Location': ['Chicago', 'Paris', 'Chicago', 'Paris'],
'Sales': [150, 200, 300, 350]
})
tot = []
def func(g, iterdf, len_df1, i = 0):
global tot
kv = {g['Primary_Location'].iloc[i]:g['Sales'].iloc[i] for i in range(len(g))}
while i < len_df1:
row = next(iterdf)[1]
# Select specific df1 rows to modify by ID and Year criteria
if g['ID'][g.index[0]]==row['ID'] and g['Year'][g.index[0]]==row['Year']:
tot.append(row['Sales'] + sum([kv[town] for town in row['Secondary_Location'].split(',') if town in kv]))
i+=1
df2.groupby(['ID', 'Year'], sort=False).apply(lambda g: func(g, df1.iterrows(), len(df1)))
df1['Sales'] = tot
print(df1)
Результат :
ID Year Primary_Location Secondary_Location Sales
0 11 2023 NewYork Chicago 250
1 11 2023 Lyon Chicago,Paris 550
2 11 2023 Berlin Paris 500
3 12 2022 Newyork Chicago 450
4 12 2022 Lyon Chicago,Paris 900
5 12 2022 Berlin Paris 750
Вы уверены в результате в строке 3, мой скрипт нашел 450, а не 400?
Объяснение :
1 - group(...).apply(...) отправляет две группы из df2 одну за другой в func() :
ID Year Primary_Location Sales
0 11 2023 Chicago 150
1 11 2023 Paris 200
ID Year Primary_Location Sales
2 12 2022 Chicago 300
3 12 2022 Paris 350
2 - kv возвращает словари из df2 следующим образом:
(каждая итерация соответствует группе, т.е. ID + год)
call 1 - {'Chicago': 100, 'Paris': 200}
call 2 - {'Chicago': 300, 'Paris': 350}
3 - Функция while, за которой следует использование next(iterator), позволяет исследовать строки в g (группе) одну за другой:
while i < len_df1:
row = next(iterdf)[1]
...
i+=1
4 - Условие if в цикле while позволяет отфильтровать df1 строк таким образом, чтобы ID и Year соответствовали строкам df2.
И для каждой корреспонденции добавить в глобальный список tot df1 и df2 значения продаж
5 — tot — это глобальный список для запоминания значений, который передается в df1 для создания столбца Sales:
df1['Sales'] = tot
Результат с новым образцом фреймов данных:
ID Year Primary_Location Secondary_Location Sales
0 11 2023 NewYork Chicago 250
1 11 2023 Berlin Chicago 450
2 11 2022 Newyork Chicago 450
3 11 2022 Berlin Chicago 700
Iterdf — это итератор pandas df1.iterrows(), который позволяет вам читать фрейм данных построчно, например, нарезая матрицу по строкам: pandas.pydata.org/docs/reference/api/… подробнее расскажу позже)
Я немного модифицирую скрипт, он будет работать с новым фреймворком данных
Это должно работать:
s = 'Secondary_Location'
(df1.assign(Secondary_Location = lambda x: x[s].str.split(','))
.explode(s)
.join(df2.set_index(['ID','Year','Primary_Location'])['Sales'].rename('Sales_2'),on = ['ID','Year',s])
.groupby(level=0)['Sales_2'].sum()
.add(df1['Sales']))
Или
df3 = (df1.assign(Secondary_Location = df1['Secondary_Location'].str.split(',')) #split Secondary_Location column into list and explode it so each row has one value
.explode('Secondary_Location'))
(df3[['ID','Year','Secondary_Location']].apply(tuple,axis=1) #create a series where ID, Year and Secondary_Location are a combined into a tuple so we can map our series created below to bring in the values needed.
.map(df2.set_index(['ID','Year','Primary_Location'])['Sales']) #create a series with lookup values in index, and make a series by selecting Sales column
.groupby(level=0).sum() #when exploding the column above, the index was repeated, so groupby(level=0).sum() will combine back to original form.
.add(df1['Sales'])) #add in original sales column
Оригинальный ответ:
s = 'Secondary_Location'
(df.assign(Secondary_Location = lambda x: x[s].str.split(','))
.explode(s)
.join(df2.set_index(['ID','Year','Primary_Location'])['Sales'].rename('Sales_2'),on = ['ID','Year',s])
.groupby(level=0)
.agg({**dict.fromkeys(df,'first'),**{s:','.join,'Sales_2':'sum'}})
.assign(Sales = lambda x: x['Sales'] + x['Sales_2'])
.drop('Sales_2',axis=1))
Выход:
ID Year Primary_Location Secondary_Location Sales
0 11 2023 NewYork Chicago 250
1 11 2023 Lyon Chicago,Paris 550
2 11 2023 Berlin Paris 500
3 12 2022 Newyork Chicago 450
4 12 2022 Lyon Chicago,Paris 900
5 12 2022 Berlin Paris 750
Привет @ rhug123, большое спасибо за ваш ответ, мне это нравится, и это работает :). не могли бы вы дать мне более подробную информацию о том, что происходит в этой части кода?: df3[['ID','Year','Secondary_Location']].apply(tuple,axis=1) .map(df2. set_index(['ID','Year','Primary_Location'])['Продажи']) .groupby(level=0).sum() .add(df1['Продажи']))
Я добавил несколько комментариев выше
К сожалению, решение не работает для немного другого фрейма данных. Не могли бы вы мне помочь :). Добавлю в описание новый датафрейм. Если у меня есть фрейм данных без строк, содержащих ",". Кажется, что новая колонка продаж удваивается. Есть ли способ сделать решение более общим, чтобы включить этот пограничный случай? @rhug123
Я просто запустил его с новыми данными и получил ожидаемый результат, используя одно из первых двух решений выше. Не могли бы вы перепроверить, когда у вас будет возможность?
Извините, я снова изменил свое описание, чтобы отразить мой фактический фрейм данных/проблему. Я обычно группирую с тремя столбцами, в редактировании я добавил столбец «День». Таким образом, группировка будет по дням, годам, идентификаторам. в некоторых крайних случаях идентификатор и день совпадают для нескольких строк. затем код ломается, выдавая ошибку повторной индексации. Я думаю, потому что индексы больше не уникальны. @rhug123
Это исходит из этой части: map(df2.set_index .. @rhug123
Включая ваш новый столбец дня в часть заданного индекса, и при выборе столбцов должно работать. просто поставьте day перед id в списке имен столбцов
Не могли бы вы объяснить мне свое решение, мне немного трудно его понять. что такое iterdf? @Лоран Б.