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

#python #pandas #dataframe

Вопрос:

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

Фрейм данных 1:

Фрукты Овощ
Манго Шпинат
Apple Капуста
Арбуз Раздавить
Персик Цуккини

Фрейм данных 2:

Предмет Цена/фунт
Манго 2
Шпинат 1
Apple 4
Персик 2
Цуккини 1

Я хочу удалить строки из фрейма данных 1, когда оба столбца отсутствуют в серии «Элемент» фрейма данных 2, и я хочу создать следующий фрейм данных 3 на основе фреймов данных 1 и 2:

Фрукты Овощ Цена Комбинации
Манго Шпинат 3
Персик Цуккини 3

Третий столбец в фрейме данных 3 представляет собой сумму цен на товары из фрейма данных 2.

Ответ №1:

Сочетание melt , merge и unstack :

 (df1[(df1['Fruit'].isin(df2['Item'])) amp; (df1['Vegetable'].isin(df2['Item']))]
    .reset_index()
    .melt(id_vars = 'index',value_vars = ['Fruit','Vegetable'])
    .merge(df2,left_on='value',right_on = 'Item')
    .drop(columns = 'Item')
    .set_index(['index','variable']).unstack(level = 1)
    .transform(lambda g: g.assign(Combination_Price=g.xs('Price/lb',axis=1,level=0).sum(axis=1)))
)
 

производит комбинированную цену и разбивку по ингредиентам, на всякий случай

             value               Price/lb           Combination_Price
variable    Fruit   Vegetable   Fruit   Vegetable   
index                   
0           Mango   Spinach     2       1         3
3           Peach   Zucchini    2       1         3
 

Ответ №2:

Вы можете сделать это в два этапа:

  1. Замаскируйте свой фрейм данных 1 таким образом, чтобы он содержал только строки, из которых в фрейме данных 2 выходят как фрукты, так и овощи.Элемент
  2. Используйте Series.map для получения значений, связанных с оставшимися строками, и сложите их вместе, чтобы получить цену комбинации.
 # Make our df2 information easier to work with. 
#  It is now a Series whose index is the Item and values are the prices. 
#  This allows us to work with it like a dictionary
>>> item_pricing = df2.set_index("Item")["Price/lb"]
>>> items = item_pricing.index

# get rows where BOTH fruit is in items amp; Vegetable is in items
>>> mask = df1["Fruit"].isin(items) amp; df1["Vegetable"].isin(items)
>>> subset = df1.loc[mask].copy()  # .copy() tells pandas we want this subset to be independent of the larger dataframe
>>> print(subset)
   Fruit Vegetable
0  Mango   Spinach
3  Peach  Zucchini

# On each column (fruit and vegetable) use .map to obtain the price of those items
#  then sum those columns together into a single price
>>> subset["combo_price"] = subset.apply(lambda s: s.map(item_pricing)).sum(axis=1)
>>> print(subset)
   Fruit Vegetable  combo_price
0  Mango   Spinach            3
3  Peach  Zucchini            3
 

Все вместе без комментариев:

 item_pricing = df2.set_index("Item")["Price/lb"]
items = item_pricing.index

mask = df1["Fruit"].isin(items) amp; df1["Vegetable"].isin(items)
subset = df1.loc[mask].copy()
subset["combo_price"] = subset.apply(lambda s: s.map(item_pricing)).sum(axis=1)
 

Комментарии:

1. Такое элегантное решение! Она охватывает большую его часть. Однако, если в столбце «Цена/фунт» указано значение NaN, последняя строка кода не работает.

2. Я попробовал nansum(), но это не решение.

3. Ну, это зависит от того, как вы хотите рассчитать цену комбинации, содержащей a NaN . Приведенный здесь код приведет к комбинации всего, что должно NaN остаться NaN , что я считаю подходящим. Что ты имел в виду?

4. Я подумываю о замене значений NaN на 0.

5. замените первую строку item_pricing = df2.set_index("Item")["Price/lb"] на эту item_pricing = df2.set_index("Item")["Price/lb"].fillna(0) , и она должна работать.

Ответ №3:

Вы можете сочетать stack() map() с unstack() sum() :

 df3 = (df1.join(df1
    .stack().map(df2.set_index('Item')['Price/lb'])
    .unstack().sum(axis=1, min_count=2).rename('Combination Price')
).dropna())

#    Fruit Vegetable  Combination Price
# 0  Mango   Spinach                3.0
# 3  Peach  Zucchini                3.0
 

Пошаговое объяснение

Стек df1 , чтобы мы могли сопоставить цены сразу:

 stacked = df1.stack().map(df2.set_index('Item')['Price/lb'])

# 0  Fruit        2.0
#    Vegetable    1.0
# 1  Fruit        4.0
#    Vegetable    NaN
# 2  Fruit        NaN
#    Vegetable    NaN
# 3  Fruit        2.0
#    Vegetable    1.0
# dtype: float64
 

Распакуйте обратно в исходную форму:

 unstacked = stacked.unstack()

#    Fruit  Vegetable
# 0    2.0        1.0
# 1    4.0        NaN
# 2    NaN        NaN
# 3    2.0        1.0
 

Сумма с min_count=2 которой означает, что сумма будет nan равна, если не существует 2 значений (как фруктовых, так и овощных)

 combo = unstacked.sum(axis=1, min_count=2)

# 0    3.0
# 1    NaN
# 2    NaN
# 3    3.0
# dtype: float64
 

Присоединяйтесь df1 и отбросьте nan строки:

 df3 = df1.join(combo.rename('Combination Price')).dropna()

#    Fruit Vegetable  Combination Price
# 0  Mango   Spinach                3.0
# 3  Peach  Zucchini                3.0
 

Ответ №4:

Вы можете сделать это с помощью двух внутренних соединений, как показано ниже. Конечный результат содержится в df3.

 df_temp = pd.merge(df1, df2, left_on='Fruit', right_on='Item', how='inner')
df3 = pd.merge(df_temp, df2, left_on='Vegetable', right_on='Item', how='inner')
df3['Combined price'] = df3['Price/lb_x']   df3['Price/lb_y']
df3.drop(columns = ['Item_x','Price/lb_x','Item_y','Price/lb_y'], inplace = True)