Как я могу интерполировать на основе значений индекса при использовании панды 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]