2

本質的に割り当ての問題があります。

私が持っているもの: 国勢調査区のような小さな地理的領域の観測があります。それぞれについて、4 つの異なる年齢層の人数を数えています。各トラクトはサブリージョンに属しています。

これで、小面積の分布が完全に正しいわけではないことがわかりました。なぜなら、より高いレベルの集計、サブリージョン レベル、およびより細かい地域レベルのデータを合計すると、異なるグループの合計が示されるという正しい分布がわかっているからです。

私が望むもの: 4つのグループにまたがるトラクトレベルの非集計分布を調整して、正しいことが知られている4つのグループにまたがるサマリーレベルの分布と一致するようにしたいが、トラクトレベルのシグナルを保持したい分布--つまり、より粗いデータに基づいて調整しますが、ウィンドウから放り出さないでください。

そこで、私がやりたいことは、次の基準を満たして、地域レベルの人口数を余白にシフトすることです。最初の 2 つは最も重要です (これらすべてを満たすことに関してはトレードオフがあることを認識しています)。

  1. 集計すると、サブリージョンの合計と一致する必要があります。
  2. 調整によって地域レベルの個体数が変化してはなりません。
  3. 既存の空間分布は実質的に変更されるべきではありませんが、新しい小地域の合計に従ってわずかに調整されただけです
  4. 理想的には、調整は公平であるべきです。つまり、調整は少数の記録だけでなく、各地域内により分散されるべきです。

以下は、モック データとプレースホルダー コードです。

まず、小領域データ:

n=1000
np.random.seed(123)
df_small_area_scale = pd.DataFrame(data={
        'grp1':np.random.randint(10,250,n),
        'grp2':np.random.randint(10,250,n),
        'grp3':np.random.randint(10,250,n),
        'grp4':np.random.randint(10,250,n),
        'subregion': np.random.choice(['A', 'B', 'C', 'D', 'E'],n),
        'tract_id':range(1000)}).set_index(['subregion','tract_id'])


df_small_area_scale.head()
                    grp1  grp2  grp3  grp4
subregion tract_id                        
B         0          119    85    11    19
D         1          136   100    46   239
A         2           76    26   198   109
B         3          230   180    84   222
A         4          108   101   222   244

そして、これを集計すると、次のsubregionようになります。

df_small_area_scale.groupby(level=0).sum()
            grp1   grp2   grp3   grp4
subregion                            
A          27241  27050  27471  26215
B          26507  24696  23315  24857
C          27474  28871  28882  28743
D          26671  26163  25077  27612
E          22739  23077  23797  24473

(そして、各グループの各サブリージョンの目標シェアを取得しましょう)

summary_area_scale_shares = summary_area_scale.stack().groupby(level=0).apply(lambda x: x/float(x.sum()))
summary_area_scale_shares.head()

subregion      
A          grp1    0.244444
           grp2    0.266667
           grp3    0.244444
           grp4    0.244444
B          grp1    0.255319
dtype: float64

第二に、小地域レベルで、小地域のデータを合計するとどうなるか。

これらは、準地域の「既知の」分布です。地区レベルのデータを調整して、地区を集計したときに、各グループのこれらの地域の合計に大まかに一致するようにしたいのはこれらです。具体的にはgrp4subregion A合計で 26,215 になりますが、ターゲットごとに22,000grp4にする必要があるため、サブリージョン A の地区では、他のグループのいくつかに再分類された人物が表示されるはずです。

summary_area_scale = pd.DataFrame(data={'grp1':[22000,24000,21000,25000,28000],
                                        'grp2':[24000,22000,26000,20000,28000],
                                        'grp3':[22000,24000,21000,25000,28000],
                                        'grp4':[22000,24000,21000,25000,28000],
                                        'subregion':list('ABCDE')}).set_index('subregion')
summary_area_scale
            grp1   grp2   grp3   grp4
subregion                            
A          22000  24000  22000  22000
B          24000  22000  24000  24000
C          21000  26000  21000  21000
D          25000  20000  25000  25000
E          28000  28000  28000  28000

1 つのアイデアは、各サブリージョン内の区画をサンプリングし、あるビンから別のビンに移動する必要がある人の総数にある程度比例して人を移動することですが、上記の基準を満たす賢い方法があるかどうかはわかりません.

私に問題を引き起こしているのは、主に、記録レベルの合計を維持し、信号として保持したい既存の空間分布を完全に破棄せずに、サブリージョンの合計に一致するようにグループ間で人々を再割り当てする方法を特定することです (しかし、現在知られている異なる全体的な分布に調整されています)。

大まかに言えば、詳細な分布をより集約的な分布に適合させる方法についてのアイデアはありgrp4 -> grp3ますgrp2 -> grp1か?

プレースホルダー コード

この関数は主に、各グループの地域シェアを含むテーブルのルックアップであり、その分布を各トラクトにプッシュするため、データ バインディングを設定する以外は何もしません。

def some_redistribution_algorithm(df):
    """
    how many persons need to be moved across groups in each subregion?
    minimal solution is to just take those shifts and apply uniformly
    tracts keep the same counts, but the *distribution* across bins will change slightly
    Quality criteria for algorithm:
    - switch population at tract level such that 
    - tract-level population counts maintained
    - Pre- and post-adjustment spatial distribution be largely unchanged
    - change is not disproportional / dramatically impacting some tracts over others 
      (i.e. a tract with 10 grp4 population losing 8 would lose 80%, while a tract with 100 q4 hhs would lose 8%)

    """

    adjustments = summary_area_scale.xs(df.name)

    size = (adjustments).apply(lambda x: abs(x)).loc['grp4'].astype(np.int64)/df.shape[0]
    print "Processing %s (%s tracts), beg. pop: %s, avg pop to move (here q4) %s" %(df.name,df.shape[0],
                                                                                   df.sum().loc['grp4'].astype(np.int64),size)
    print 'Average pop per tract:'
    print df.sum()/df.shape[0]


    ## tract-level distribution, if all tracts had the same distribution within each subregion (placeholder)

    return df_small_area_scale.xs(df.name).mul(summary_area_scale_shares.unstack().xs(df.name),axis=1)

    #samplerows= np.random.choice(a=df.index, size=size)
    #df.loc[samplerows,:] = df.loc[samplerows,:]#, p=df.totalshare.tolist()),:]
df_small_area_scale.groupby(level=0).apply(some_redistribution_algorithm)
4

1 に答える 1

1

あなたの質問を正しく理解していれば、反復比例フィッティングがあなたが探しているものかもしれないと思います。もしよろしければ、最近私が抱えていた同様の問題について述べたいと思います。これは私が解決しようとしていた問題です:

大都市レベルでの年齢分布は知っていますし、各地区の総人口も知っていますが、国勢調査の仕組みから、各地区の年齢別分布はわかっていると思いますが、よくわかりません。

地区内の総人口 (行の境界線) を満たしたいことはわかっています。大都市レベルでの年齢分布 (列の境界線) を満たしたいこともわかっています。また、各地区の分布を使用して ipf を「シード」したいこともわかっています。これが私の最良の答えです。もちろん、それは機能しません (つまり、数値が加算されないということです)。そのため、限界を満たすために、その推測からすぐに逸脱します。そして、これが反復比例フィッティングの目的です。

おそらく防弾ではありませんが、私が使用したコード(Python / numpy)は次のとおりです。

# this should be fairly self explanitory if you know ipf
# seed_matrix is your best bet at the totals, col_marginals are
# observed column marginals and row_marginals is the same for rows

def simple_ipf(seed_matrix, col_marginals, row_marginals, tolerance=1, cnt=0):
    assert np.absolute(row_marginals.sum() - col_marginals.sum()) < 5.0

    # first normalize on columns
    ratios = col_marginals / seed_matrix.sum(axis=0)
    seed_matrix *= ratios
    closeness = np.absolute(row_marginals - seed_matrix.sum(axis=1)).sum()
    assert np.absolute(col_marginals - seed_matrix.sum(axis=0)).sum() < .01
    # print "row closeness", closeness
    if closeness < tolerance:
        return seed_matrix

    # first normalize on rows
    ratios = row_marginals / seed_matrix.sum(axis=1)
    ratios[row_marginals == 0] = 0
    seed_matrix = seed_matrix * ratios.reshape((ratios.size, 1))
    assert np.absolute(row_marginals - seed_matrix.sum(axis=1)).sum() < .01
    closeness = np.absolute(col_marginals - seed_matrix.sum(axis=0)).sum()
    # print "col closeness", closeness
    if closeness < tolerance:
        return seed_matrix

    if cnt >= 50:
        return seed_matrix

    return simple_ipf(seed_matrix, col_marginals, row_marginals,
                      tolerance, cnt+1) 
于 2015-10-28T00:54:49.430 に答える