Панды: как вы сопоставляете словарь словарей с 2 столбцами?

#python #pandas #dataframe #dictionary

Вопрос:

У меня есть следующий словарь:

 rates = {'USD': 
              {'2019': 1,
               '2020': 2,
               '2021': 3},
         'CAD':
              {'2019': 4,
               '2020': 5,
               '2021': 6}
         }
 

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

    Item Currency Year Rate
0  1    USD      2019 
1  2    USD      2020
2  3    CAD      2021
3  4    CAD      2019
4  5    GBP      2020
 

Теперь я хочу заполнить столбец Rate , сопоставив правильную скорость, где rate = f(currency,year) . Я пытаюсь с:

 def map_rate(data, rates):

    for index, row in data.iterrows():

        currency = str(row['Currency'])

        if currency in list(rates.keys()):

            year = str(row['Year'])
            rate = rates[currency][year]

        else:
            rate = 1

    return rate
 

Я использую вышесказанное следующим образом:

 df['Rate'] = map_rate(test, rates)
 

Однако это возвращает только первую ставку, например значение 1, вместо соответствующих ставок:

     Item Currency Year  Rate
0   1    USD      2019  1
1   2    USD      2020  1
2   3    CAD      2021  1
3   4    CAD      2019  1
4   5    GBP      2020  1
 

Ожидаемый результат таков:

     Item Currency Year  Rate
0   1    USD      2019  1
1   2    USD      2020  2
2   3    CAD      2021  6
3   4    CAD      2019  4
4   5    GBP      2020  1
 

В чем моя ошибка?

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

1. Примечание: вы можете напрямую проверить по словарю, существует ли ключ или нет: if currency in rates вместо if currency in list(rates.keys()) . Последний формирует список и теряет ~O(1) времени поиска.

Ответ №1:

Вот один из способов: создайте многоиндексный ряд из ставок stack , reindex используя значения из df , чтобы получить желаемую ставку за строку.

 df['rate'] = (
    pd.DataFrame(rates)
      .stack()
      .reindex(pd.MultiIndex.from_frame(df[['Year','Currency']].astype(str)), 
               fill_value=1)
     .to_numpy()
)
print(df)
   Item Currency  Year  rate
0     1      USD  2019     1
1     2      USD  2020     2
2     3      CAD  2021     6
3     4      CAD  2019     4
4     5      GBP  2020     1
 

Ответ №2:

С помощью .apply

Экс:

 df['Rate'] = df.apply(lambda x: rates[x['Currency']][x['Year']], axis=1)
# OR
df['Rate'] = df.apply(lambda x: rates.get(x['Currency'], dict()).get(x['Year'], 1), axis=1)
print(df)
 

Выход:

   Item Currency  Year  Rate
0    1      USD  2019     1
1    2      USD  2020     2
2    3      CAD  2021     6
3    4      CAD  2019     4
4    5      GBP  2020     1
 

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

1. Спасибо. Как вы думаете, почему мое решение не работает?

2. Я не уверен, почему это не работает….это, вероятно, потому if currency in list(rates.keys()): , что не удается

3. Я все равно получу значение 1 , даже если я удалю условие. Похоже, что он неправильно зацикливается на разных названиях валют.

4. @Zizzipupp это потому, что ваша функция возвращает скаляр (и в данном случае 1, потому что это последнее значение, полученное в цикле), в то время как вы должны возвращать список значений, по одному для каждой итерации

5. У вас, вероятно, плохие данные. проверьте, есть ли в нем ведущее место?

Ответ №3:

Создайте другой фрейм данных для ставок

 rates_df = pd.DataFrame(rates).unstack().reset_index()
rates_df.columns = ['Currency', 'Year', 'Rates']
rates_df['Year'] = rates_df['Year'].astype(int)
 

Затем объедините

 df.merge(rates_df, on=['Currency', 'Year'], how='left').fillna(1)
 

Фрейм данных ставок

   Currency  Year  Rates
0      USD  2019      1
1      USD  2020      2
2      USD  2021      3
3      CAD  2019      4
4      CAD  2020      5
5      CAD  2021      6
 

Выход

    Item Currency  Year  Rates
0     1      USD  2019    1.0
1     2      USD  2020    2.0
2     3      CAD  2021    6.0
3     4      CAD  2019    4.0
4     5      GBP  2020    1.0
 

Ответ №4:

Это можно легко сделать с помощью встроенного метода Pandas df.apply() . Вот более подробный пример, чем другие опубликованные ответы.

Код:

 def get_rate(row):
  if row['Currency'] in rates.keys():
    return rates[row['Currency']][row['Year']]
  else:
    return 1

df['Rate'] = df.apply(get_rate,axis=1)

print(df)