Как я могу интерполировать на основе значений индекса при использовании панды MultiIndex?

У меня есть данные демографической панели, где каждая точка данных классифицируется по стране, полу, году и возрасту. Для данной страны, пола и года в моем возрасте отсутствуют данные, и я хочу интерполировать их на основе значения возраста. Например, если 5-летние имеют значение 5, а 10-летние имеют значение 10, 6,3-летние должны иметь значение 6,3. Я не могу использовать метод линейной интерполяции панд по умолчанию, потому что мои возрастные группы не расположены линейно. Мои данные выглядят примерно так:

iso3s = ['USA', 'CAN']
age_start_in_years = [0, 0.01, 0.1, 1]
years = [1990, 1991]
sexes = [1,2]
multi_index = pd.MultiIndex.from_product([iso3s,sexes,years,age_start_in_years],
                                          names = ['iso3','sex','year','age_start'])

frame_length = len(iso3s)*len(age_start_in_years)*len(years)*len(sexes)
test_df = pd.DataFrame({'value':range(frame_length)},index=multi_index)
test_df=test_df.sortlevel()

# Insert missingness to practice interpolating
test_df.loc[idx[:,:,:,[0.01,0.1]],:] = np.NaN
test_df

                                value
iso3    sex year    age_start   
CAN     1   1990    0.00        0
                    0.01        NaN
                    0.10        NaN
                    1.00        3
            1991    0.00        4
                    0.01        NaN
                    0.10        NaN
                    1.00        7
       2    1990    0.00        8
...

Тем не менее, когда я пытаюсь использовать test_df.interpolate(method='index')Я получаю эту ошибку:

ValueError: Only `method=linear` interpolation is supported on MultiIndexes.

Конечно, должен быть какой-то способ интерполяции на основе значений индекса.

4 ответа

Это может быть немного поздно, но я столкнулся с той же проблемой сегодня. То, что я придумал, - это тоже просто обходной путь, но он использует встроенные панды, по крайней мере. Мой подход состоял в том, чтобы сбросить индекс, а затем сгруппировать по первому подмножеству столбцов индекса (т.е. все, кроме age_start). Эти субкадры могут затем быть интерполированы с method='index' параметр и положить обратно в целый кадр с pd.concat, Результирующий DataFrame затем получает свой исходный индекс переназначается.

idx_names = test_df.index.names
test_df = test_df.reset_index()
concat_list = [grp.set_index('age_start').interpolate(method='index') for _, grp in test_df.groupby(['iso3', 'sex', 'year'])]
test_df = pd.concat(concat_list)
test_df = test_df.reset_index().set_index(idx_names)
test_df
                         value
iso3 sex year age_start       
CAN  1   1990 0.00       16.00
              0.01       16.03
              0.10       16.30
              1.00       19.00
         1991 0.00       20.00
              0.01       20.03
              0.10       20.30
              1.00       23.00
     2   1990 0.00       24.00

Я нашел этот хакерский обходной путь, который избавляет от MultiIndex и использует комбинацию groupby и transform:

def multiindex_interp(x, interp_col, step_col):

    valid = ~pd.isnull(x[interp_col])
    invalid = ~valid

    x['last_valid_value'] = x[interp_col].ffill()
    x['next_valid_value'] = x[interp_col].bfill()

    # Generate a new Series filled with NaN's
    x['last_valid_step'] =  np.NaN
    # Copy the step value where we have a valid value
    x['last_valid_step'][valid] = x[step_col][valid]
    x['last_valid_step'] = x['last_valid_step'].ffill()

    x['next_valid_step'] =  np.NaN
    x['next_valid_step'][valid] = x[step_col][valid]
    x['next_valid_step'] = x['next_valid_step'].bfill()

    # Simple linear interpolation= distance from last step / (range between closest valid steps) *
    #                              difference between closest values + last value
    x[interp_col][invalid] = (x[step_col]-x['last_valid_step'])/(x['next_valid_step'] - x['last_valid_step']) \
                             * (x['next_valid_value']-x['last_valid_value']) \
                             + x['last_valid_value']
    return x

test_df = test_df.reset_index(drop=False)
grouped = test_df.groupby(['iso3','sex','year'])
interpolated = grouped.transform(multiindex_interp,'value','age_start')
test_df['value'] = interpolated['value']
test_df
    iso3    sex year    age_start   value
0   CAN     1   1990    0.00        16.00
1   CAN     1   1990    0.01        16.03
2   CAN     1   1990    0.10        16.30
3   CAN     1   1990    1.00        19.00
4   CAN     1   1991    0.00        20.00
5   CAN     1   1991    0.01        20.03
6   CAN     1   1991    0.10        20.30
7   CAN     1   1991    1.00        23.00
8   CAN     2   1990    0.00        24.00
9   CAN     2   1990    0.01        24.03
10  CAN     2   1990    0.10        24.30
11  CAN     2   1990    1.00        27.00
...

Вы можете попробовать что-то вроде этого:

test_df.groupby(level=[0,1,2])\
       .apply(lambda g: g.reset_index(level=[0,1,2], drop=True)
                         .interpolate(method='index'))

Выход:

                         value
iso3 sex year age_start       
CAN  1   1990 0.00       16.00
              0.01       16.03
              0.10       16.30
              1.00       19.00
         1991 0.00       20.00
              0.01       20.03
              0.10       20.30
              1.00       23.00
     2   1990 0.00       24.00
              0.01       24.03
              0.10       24.30
              1.00       27.00
         1991 0.00       28.00
              0.01       28.03
              0.10       28.30
              1.00       31.00
USA  1   1990 0.00        0.00
              0.01        0.03
              0.10        0.30
              1.00        3.00
         1991 0.00        4.00
              0.01        4.03
              0.10        4.30
              1.00        7.00
     2   1990 0.00        8.00
              0.01        8.03
              0.10        8.30
              1.00       11.00
         1991 0.00       12.00
              0.01       12.03
              0.10       12.30
              1.00       15.00

Это сработало для меня:

      test_df["value"]=test_df.reset_index().groupby(["iso3","sex","year"]).apply(
    lambda group: group[["age_start","value"]].set_index("age_start").
    interpolate()).reset_index(drop=True)

Это берет DF и сбрасывает/отменяет созданный вами индекс, а затем разделяет DF, используя правильный первичный ключ (pk).["iso3","sex","year"]. Затем он применяет интерполяцию к каждой группе, используя методpd.DataFrame.apply()и наш другlambda: Первая настройка'age_start'в качестве индекса, поэтому интерполяция очень проста для'value'k для каждой группы уникальных p, а затем с помощью методаpd.DataFrame.interpolate(). Наконец, сброса индекса должно быть достаточно для получения окончательной интерполяции.

Наконец, я сделал это еще и потому, что хотел сделать то же самое, но для многих столбцов. Используя тот же код, подход будет таким же, но в очень простом цикле for:

      for n in range(1,32):
    col="value"+str(n)
    test_df[col] = test_df.reset_index().groupby(["iso3","sex","year"]).apply(
        lambda group: group[["age_start",col]].set_index("age_start").interpolate()
    ).reset_index(drop=True)[col]
Другие вопросы по тегам