在使用pandas MultiIndex时,如何基于索引值进行插值?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在使用pandas MultiIndex时,如何基于索引值进行插值?相关的知识,希望对你有一定的参考价值。

我有人口统计面板数据,其中每个数据点按国家/地区,性别,年份和年龄进行分类。对于给定的国家,性别和年份,我的年龄模式缺少数据,我想根据年龄的值进行插值。例如,如果5岁儿童的值为5,而10岁儿童的值为10,则6.3岁儿童的值应为6.3。我不能使用默认的pandas'线性'插值方法,因为我的年龄组没有线性间隔。我的数据看起来像这样:

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
idx = pd.IndexSlice
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.

当然必须有一些基于索引值进行插值的方法。

答案

我发现这个hacky解决方法摆脱了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
...
另一答案

这可能会有点晚,但我今天遇到了同样的问题。我想出的也只是一种解决方法,但它至少使用了内置的pandas。我的方法是重置索引,然后按索引列的第一个子集(即除了age_start之外的所有子集)进行分组。然后可以使用method='index'参数对这些子DataFrame进行插值,并将其与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

编辑

我今天回到了这个问题,并在我最初提出的解决方案中发现了一个错误。如果未在示例中对多索引进行排序,则上述代码会按索引值对DataFrame进行排序。为了解决这个问题,我将结果返回到具有原始索引的DataFrame中,以便保留索引顺序。我也把它放在一个函数中。

def interp_multiindex(df, interp_idx_name):
    """
    Provides index-based interpolation for pd.Multiindex which usually only support linear
    interpolation. Interpolates full DataFrame.

    Parameters
    ----------
    df : pd.DataFrame
        The DataFrame with NaN values
    interp_idx_name : str
        The name of the multiindex level on which index-based interpolation should take place

    Returns
    -------
    df : pd.DataFrame
        The DataFrame with index-based interpolated values
    """
    # Get all index level names in order
    existing_multiidx = df.index
    # Remove the name on which interpolation will take place
    noninterp_idx_names = [idx_name for idx_name in existing_multiidx.names 
                           if idx_name != interp_idx_name]
    df = df.reset_index()
    concat_list = [grp.set_index(interp_idx_name).interpolate(method='index') 
                   for _, grp in df.groupby(noninterp_idx_names)]
    df = pd.concat(concat_list)
    df = df.reset_index().set_index(existing_multiidx.names)
    df = pd.DataFrame(index=existing_multiidx).join(df)
    return df
另一答案

你可以尝试这样的事情:

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

以上是关于在使用pandas MultiIndex时,如何基于索引值进行插值?的主要内容,如果未能解决你的问题,请参考以下文章

使用 MultiIndex 时,如何将此 Pandas 列类型保留为日期时间?

合并pandas DataFrames时如何保留列MultiIndex值

pandas - 如何使用 MultiIndex 在 DataFrame 的深层检索最小值索引

如何使用 pandas multiIndex 查询多列

Pandas DataFrame - 如何检索 MultiIndex 级别的特定组合

如何更新 MultiIndex pandas DataFrame 的子集