转移分解分布以匹配更多聚合水平分布
Posted
技术标签:
【中文标题】转移分解分布以匹配更多聚合水平分布【英文标题】:Shifting disaggregate distribution to match more aggregate level distribution 【发布时间】:2016-01-26 05:46:34 【问题描述】:我有什么本质上是分配问题。
我有什么: 我观察了一些小的地理区域,比如人口普查区。对于每一个,我都有四个不同年龄组的人数。每个区域都属于一个子区域。
现在,我知道小区域分布并不完全正确,因为我知道 正确 分布——在更高的聚合级别、子区域级别和更精细的区域级别数据,相加时,显示不同的组总数。
我想要什么: 我想调整我的区域级别,四组的分解分布,使其与已知正确的四个组的摘要级别分布一致,但保留区域级别分布的信号——即根据更粗略的数据对其进行调整,但不要将其扔出窗外。
然后,我想做的是将区域级人口计数转移到边缘,满足以下标准,前两个是最重要的(我意识到在满足所有这些方面需要权衡) :
-
在汇总时,它应该与次区域总数相匹配。
调整不应改变区域级别的人口。
现有的空间分布不应该有实质性的改变,但我只是根据新的次区域总数稍微调整
理想情况下,调整应该是公平的——即调整不应只针对几条记录,而应更多地分布在每个区域内。
下面是模拟数据和占位符代码:
一、小面积数据:
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
其次,小区域数据应该在次区域级别汇总到什么。
这些是次区域的“已知”分布。正是这些我希望调整区域级别的数据,以便在聚合区域时,它们大致匹配每个组中的这些区域总数。具体来说,grp4
在subregion A
中的总和为 26,215,但根据目标,它应该是 22,000,因此子区域 A 中的人应该看到从 grp4
重新分类到其他一些人团体。
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
一个想法是在每个子区域内对区域进行抽样,然后按照需要从一个垃圾箱转移到另一个垃圾箱的总人数按一定比例移动人员,尽管我不确定是否有一种聪明的方法来满足上述标准。
导致我遇到问题的主要是这确定了一种在各组之间重新分配人员以匹配次区域总数的方法,同时保持创纪录水平的总数,而不是完全抛弃预先存在的空间分布,我想保留它作为信号(但调整到现在已知的不同整体分布)。
关于如何使详细分布更适合总体分布的任何想法,而不仅仅是采样区域和从grp4 -> grp3
、grp2 -> grp1
移动 x 人以及现有和现有之间的差异。目标分布?
占位符代码
这个函数主要是在一个表中查找每个组中的区域份额,将该分布推送到每个区域,因此它除了设置数据绑定之外什么都不做。
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)
【问题讨论】:
我在理解您的动机时遇到了一些麻烦,但在我看来,您可以通过用随机样本替换每个“小区域”组来实现您正在寻找的效果从相应的聚合“大区域”组中统一绘制相同大小。这样一来,您就可以为每个“小区域”保持相同的总人口数,同时保证每个区域内各组的联合计数分布平均与相应的“大区域”相匹配。 @ali_m,很好的建议(我认为它很好,我立即尝试了!),虽然我认为它与我的另一个(埋在文本中)标准相冲突——它的方法“没有完全抛弃……已经存在的空间分布”。 在我看来,您要求的是两个相互排斥的东西。为了强制您的子区域分布与聚合分布相匹配,您将必然必须更改子组级别的空间分布。所需调整的大小取决于子组分布与其对应的聚合分布相比的差异程度,以及您要求它们匹配的程度。从某种意义上说,这些调整不可能真正是“公平的”,因为某些次区域和群体将需要比其他更大的调整。 我认为,如果您能解释一下您的总体目标是什么,将会有所帮助。此外,目前您的问题似乎更多是关于哪种重采样程序是合适的,而不是如何在代码中实现它,因此您可能更幸运地在CrossValidated 上以独立于语言的方式提出您的问题。 @ali_m,我意识到要求之间存在一些冲突/权衡,但我认为这不是一个基本的要求:例如,您可以将某个子区域的第 4 年龄组的数字减少,而不会从根本上改变底层的区域内地图。 IE。某组已有浓度的区域,也应是调整后相对浓度的区域。 【参考方案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)
【讨论】:
这正是我所需要的。以上是关于转移分解分布以匹配更多聚合水平分布的主要内容,如果未能解决你的问题,请参考以下文章