如何用相似行的列平均值替换pandas列中的特定值?

4

问题

我目前拥有一个来自这个 Kaggle 数据集中的房产信息的 pandas dataframe。以下是该数据集中的示例 dataframe:

| neighborhood  | borough | block | year built | ... |
------------------------------------------------------
| Annadale      | 5       | 5425  | 2015       | ... |
| Woodside      | 4       | 2327  | 1966       | ... |
| Alphabet City | 1       | 396   | 1985       | ... |
| Alphabet City | 1       | 405   | 1996       | ... |
| Alphabet City | 1       | 396   | 1986       | ... |
| Alphabet City | 1       | 396   | 1992       | ... |
| Alphabet City | 1       | 396   | 0          | ... |
| Alphabet City | 1       | 396   | 1990       | ... |
| Alphabet City | 1       | 396   | 1984       | ... |
| Alphabet City | 1       | 396   | 0          | ... |

我想做的是将"建造年份"列中值为零的每一行替换为在相同社区、区域和街区内具有相同特征的行的"建造年份"值的中位数。在某些情况下,{社区、区域、街区}集合中存在多个具有"建造年份"列中零值的行。这在上面的示例数据帧中是显示出来的。
为了说明问题,我将这两行放到示例数据帧中。
| neighborhood  | borough | block | year built | ... |
------------------------------------------------------
| Alphabet City | 1       | 396   | 0          | ... |
| Alphabet City | 1       | 396   | 0          | ... |

为了解决这个问题,我想使用所有其他行中具有相同邻居、区和街区的“建造年份”值的平均值来填写“建造年份”列中为零的行。对于示例行,邻里是"Alphabet City",市区是1,街区是396,因此我将使用以下匹配行从示例数据框中计算平均值:
| neighborhood  | borough | block | year built | ... |
------------------------------------------------------
| Alphabet City | 1       | 396   | 1985       | ... |
| Alphabet City | 1       | 396   | 1986       | ... |
| Alphabet City | 1       | 396   | 1992       | ... |
| Alphabet City | 1       | 396   | 1990       | ... |
| Alphabet City | 1       | 396   | 1984       | ... |

我会取那些行的“year built”列的平均值(即1987.4),并用这个平均值替换零值。原本有零值的行将变成这样:
| neighborhood  | borough | block | year built | ... |
------------------------------------------------------
| Alphabet City | 1       | 396   | 1987.4     | ... |
| Alphabet City | 1       | 396   | 1987.4     | ... |

我目前的代码

我所做的只是删除“建造年份”列中为零的行,并找到每个{邻域,区,街区}集合的平均年份。原始数据帧存储在raw_data中,它看起来像本帖子顶部的示例数据帧。代码如下:

# create a copy of the data
temp_data = raw_data.copy()

# remove all rows with zero in the "year built" column
mean_year_by_location = temp_data[temp_data["YEAR BUILT"] > 0]

# group the rows into {neighborhood, borough, block} sets and take the mean of the "year built" column in those sets
mean_year_by_location = mean_year_by_location.groupby(["NEIGHBORHOOD","BOROUGH","BLOCK"], as_index = False)["YEAR BUILT"].mean()

输出结果如下:

| neighborhood  | borough | block | year built | 
------------------------------------------------
| ....          | ...     | ...   | ...        |
| Alphabet City | 1       | 390   | 1985.342   | 
| Alphabet City | 1       | 391   | 1986.76    | 
| Alphabet City | 1       | 392   | 1992.8473  | 
| Alphabet City | 1       | 393   | 1990.096   | 
| Alphabet City | 1       | 394   | 1984.45    | 

那么,我该如何使用mean_year_by_location数据框中的平均“年份”值来替换原始raw_data数据框中的零值?

对于这篇长篇文章,我表示歉意。我只是想表述得更加清晰。

2个回答

5
使用 set_index + replace,然后对 mean 使用 fillna
v = df.set_index(
    ['neighborhood', 'borough', 'block']
)['year built'].replace(0, np.nan)   

df = v.fillna(v.mean(level=[0, 1, 2])).reset_index()
df

    neighborhood  borough  block  year built
0       Annadale        5   5425      2015.0
1       Woodside        4   2327      1966.0
2  Alphabet City        1    396      1985.0
3  Alphabet City        1    405      1996.0
4  Alphabet City        1    396      1986.0
5  Alphabet City        1    396      1992.0
6  Alphabet City        1    396      1987.4
7  Alphabet City        1    396      1990.0
8  Alphabet City        1    396      1984.0
9  Alphabet City        1    396      1987.4

细节

首先,设置索引,并将0替换为NaN,这样即将进行的mean计算不会受到这些值的影响 -

v = df.set_index(
    ['neighborhood', 'borough', 'block']
)['year built'].replace(0, np.nan)   

v 

neighborhood   borough  block
Annadale       5        5425     2015.0
Woodside       4        2327     1966.0
Alphabet City  1        396      1985.0
                        405      1996.0
                        396      1986.0
                        396      1992.0
                        396         NaN
                        396      1990.0
                        396      1984.0
                        396         NaN
Name: year built, dtype: float64

接下来,计算平均值mean-
m = v.mean(level=[0, 1, 2])
m

neighborhood   borough  block
Annadale       5        5425     2015.0
Woodside       4        2327     1966.0
Alphabet City  1        396      1987.4
                        405      1996.0
Name: year built, dtype: float64

这是一个映射,我们将其传递给fillnafillna相应地替换了之前引入的NaN,并用索引映射的相应平均值替换它们。完成后,只需重置索引即可恢复原始结构。

v.fillna(m).reset_index()

    neighborhood  borough  block  year built
0       Annadale        5   5425      2015.0
1       Woodside        4   2327      1966.0
2  Alphabet City        1    396      1985.0
3  Alphabet City        1    405      1996.0
4  Alphabet City        1    396      1986.0
5  Alphabet City        1    396      1992.0
6  Alphabet City        1    396      1987.4
7  Alphabet City        1    396      1990.0
8  Alphabet City        1    396      1984.0
9  Alphabet City        1    396      1987.4

@coldspeed,第6行和第9行的平均值不应该不同吗?一旦将平均值放在第6行,对于第9行,我们需要重新计算平均值,因为当我们迭代到第9行时,第6行的值已经发生了变化。 - Anil_M
@Anil_M 如果你仔细阅读OP的问题,你会发现这不是他们要求的。他们只想用该组的平均值填充NaN。 - cs95

2

我将在groupby.apply中使用mask。 我这样做只是因为我喜欢它的流畅性。 我不会声称它特别快。 尽管如此,这个答案可能提供一些关于可能的替代方案的视角。

gidx = ['neighborhood', 'borough', 'block']

def fill_with_mask(s):
    mean = s.loc[lambda x: x != 0].mean()
    return s.mask(s.eq(0), mean)

df.groupby(gidx)['year built'].apply(fill_with_mask)

0    2015.0
1    1966.0
2    1985.0
3    1996.0
4    1986.0
5    1992.0
6    1987.4
7    1990.0
8    1984.0
9    1987.4
Name: year built, dtype: float64

我们可以使用pd.DataFrame.assign创建一个数据框的副本。
df.assign(**{'year built': df.groupby(gidx)['year built'].apply(fill_with_mask)})

    neighborhood  borough  block  year built
0       Annadale        5   5425      2015.0
1       Woodside        4   2327      1966.0
2  Alphabet City        1    396      1985.0
3  Alphabet City        1    405      1996.0
4  Alphabet City        1    396      1986.0
5  Alphabet City        1    396      1992.0
6  Alphabet City        1    396      1987.4
7  Alphabet City        1    396      1990.0
8  Alphabet City        1    396      1984.0
9  Alphabet City        1    396      1987.4

可以使用列赋值在原地完成相同的任务:

df['year built'] = df.groupby(gidx)['year built'].apply(fill_with_mask)

或者

df.update(df.groupby(gidx)['year built'].apply(fill_with_mask))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接