Суммировать строки из двух разных фреймов данных на основе значения столбцов

У меня есть два фрейма данных

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
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Библиотека для работы с мороженым
Библиотека для работы с мороженым
Лично я попрощался с операторами print() в python. Без шуток.
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Привет, люди RPA, это снова я и я несу подарки! В очередном моем приключении о том, как создавать ботов для облегчения рутины. Вот, думаю, стоит...
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
Учебник по веб-скрапингу
Учебник по веб-скрапингу
Привет, ребята... В этот раз мы поговорим о веб-скрейпинге. Целью этого обсуждения будет узнать и понять, что такое веб-скрейпинг, а также узнать, как...
Тонкая настройка GPT-3 с помощью Anaconda
Тонкая настройка GPT-3 с помощью Anaconda
Зарегистрируйте аккаунт Open ai, а затем получите ключ API ниже.
1
0
115
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Не так уж и прост твой вопрос...

Предлагаемый сценарий

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? @Лоран Б.

the phoenix 12.01.2023 13:43

Iterdf — это итератор pandas df1.iterrows(), который позволяет вам читать фрейм данных построчно, например, нарезая матрицу по строкам: pandas.pydata.org/docs/reference/api/… подробнее расскажу позже)

Laurent B. 12.01.2023 19:14

Я немного модифицирую скрипт, он будет работать с новым фреймворком данных

Laurent B. 12.01.2023 21:50
Ответ принят как подходящий

Это должно работать:

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['Продажи']))

the phoenix 11.01.2023 11:57

Я добавил несколько комментариев выше

rhug123 11.01.2023 16:19

К сожалению, решение не работает для немного другого фрейма данных. Не могли бы вы мне помочь :). Добавлю в описание новый датафрейм. Если у меня есть фрейм данных без строк, содержащих ",". Кажется, что новая колонка продаж удваивается. Есть ли способ сделать решение более общим, чтобы включить этот пограничный случай? @rhug123

the phoenix 12.01.2023 13:17

Я просто запустил его с новыми данными и получил ожидаемый результат, используя одно из первых двух решений выше. Не могли бы вы перепроверить, когда у вас будет возможность?

rhug123 12.01.2023 15:09

Извините, я снова изменил свое описание, чтобы отразить мой фактический фрейм данных/проблему. Я обычно группирую с тремя столбцами, в редактировании я добавил столбец «День». Таким образом, группировка будет по дням, годам, идентификаторам. в некоторых крайних случаях идентификатор и день совпадают для нескольких строк. затем код ломается, выдавая ошибку повторной индексации. Я думаю, потому что индексы больше не уникальны. @rhug123

the phoenix 12.01.2023 16:49

Это исходит из этой части: map(df2.set_index .. @rhug123

the phoenix 12.01.2023 16:53

Включая ваш новый столбец дня в часть заданного индекса, и при выборе столбцов должно работать. просто поставьте day перед id в списке имен столбцов

rhug123 12.01.2023 16:59

Другие вопросы по теме