简介
在处理数据时,我们经常会遇到缺失或无效的数据条目。如果想在不删除这些数据的情况下跳过或标记它们,可能需要使用条件语句或过滤数据。NumPy的numpy.ma模块提供了掩码数组(masked arrays)功能,可以更方便地处理这种情况。
掩码数组是标准NumPy ndarray和掩码(mask)的组合:
- 数据部分是常规的numpy.ndarray,可以是任何形状或数据类型
- 掩码是一个布尔数组,与数据形状相同
- fill_value是一个可用于替换无效条目的值
掩码数组在以下情况下特别有用:
- 想保留被掩码的值以便后续处理,而不需要复制数组
- 需要处理多个带有各自掩码的数组
- 对缺失或无效值有不同的标记,希望保留这些标记而不是替换原始数据集中的值
- 无法避免或消除缺失值,但又不想在运算中处理NaN(Not a Number)值
此外,numpy.ma模块还提供了大多数NumPy通用函数(ufuncs)的特定实现,可以对掩码数据应用快速向量化函数和操作。
使用掩码数组分析COVID-19数据
我们将使用一个包含2020年初COVID-19疫情初期数据的CSV文件来演示掩码数组的使用。
首先导入所需的模块:
import numpy as np
import os
import matplotlib.pyplot as plt
from numpy import ma
读取数据
我们使用numpy.genfromtxt函数从CSV文件中读取数据:
filepath = os.getcwd()
filename = os.path.join(filepath, "who_covid_19_sit_rep_time_series.csv")
# 读取日期(第4-18列的第一行)
dates = np.genfromtxt(
filename,
dtype=np.str_,
delimiter=",",
max_rows=1,
usecols=range(4, 18),
encoding="utf-8-sig",
)
# 读取地理位置名称(跳过前6行,读取第1-2列)
locations = np.genfromtxt(
filename,
dtype=np.str_,
delimiter=",",
skip_header=6,
usecols=(0, 1),
encoding="utf-8-sig",
)
# 读取前14天的数值数据
nbcases = np.genfromtxt(
filename,
dtype=np.int_,
delimiter=",",
skip_header=6,
usecols=range(4, 18),
encoding="utf-8-sig",
)
数据探索
首先,我们可以绘制整个数据集的图表:
selected_dates = [0, 3, 11, 13]
plt.plot(dates, nbcases.T, "--")
plt.xticks(selected_dates, dates[selected_dates])
plt.title("COVID-19 cumulative cases from Jan 21 to Feb 3 2020")
plt.show()
从图表中可以看出,1月24日至2月1日期间的数据形状有些奇怪。我们需要进一步研究这些数据的来源。
处理中国数据
由于中国数据包含多个省份,我们需要将它们合并:
# 删除总计行
totals_row = 35
locations = np.delete(locations, (totals_row), axis=0)
nbcases = np.delete(nbcases, (totals_row), axis=0)
# 计算中国总数
china_total = nbcases[locations[:, 1] == "China"].sum(axis=0)
print(china_total)
输出结果中出现了负值,这显然是不正确的。问题在于数据中存在缺失值。
使用掩码数组处理缺失数据
我们可以使用掩码数组来正确处理缺失的数据:
nbcases_ma = ma.masked_values(nbcases, -1)
现在我们可以重新计算中国的总病例数:
china_masked = nbcases_ma[locations[:, 1] == "China"].sum(axis=0)
china_total = china_masked.data
print(china_total)
这次的结果没有负值了,但仍然存在一些问题:某些日期的累计病例数似乎在下降,这与"累计数据"的定义不符。
进一步细化中国数据
我们可以排除香港、台湾、澳门和"未指定"地区的数据,以获得更准确的中国大陆数据:
china_mask = (
(locations[:, 1] == "China")
& (locations[:, 0] != "Hong Kong")
& (locations[:, 0] != "Taiwan")
& (locations[:, 0] != "Macau")
& (locations[:, 0] != "Unspecified*")
)
china_total = nbcases_ma[china_mask].sum(axis=0)
现在我们可以绘制更准确的中国大陆数据图表:
plt.plot(dates, china_total.T, "--")
plt.xticks(selected_dates, dates[selected_dates])
plt.title("COVID-19 cumulative cases from Jan 21 to Feb 3 2020 - Mainland China")
plt.show()
数据拟合
为了估计缺失数据期间的病例数,我们可以使用简单的插值方法。首先,我们需要分离有效和无效的数据点:
invalid = china_total[china_total.mask]
valid = china_total[~china_total.mask]
# 获取有效数据对应的日期
valid_dates = dates[~china_total.mask]
然后,我们可以使用NumPy的多项式拟合功能创建一个三次多项式模型:
t = np.arange(len(china_total))
model = np.polynomial.Polynomial.fit(t[~china_total.mask], valid, deg=3)
最后,我们可以绘制一个包含实际数据和拟合曲线的图表,并估算出记录开始7天后(1月28日)的病例数:
plt.plot(t, china_total)
plt.plot(t[china_total.mask], model(t)[china_total.mask], "--", color="orange")
plt.plot(7, model(7), "r*")
plt.xticks([0, 7, 13], dates[[0, 7, 13]])
plt.yticks([0, model(7), 10000, 17500])
plt.legend(["Mainland China", "Cubic estimate", "7 days after start"])
plt.title(
"COVID-19 cumulative cases from Jan 21 to Feb 3 2020 - Mainland China\n"
"Cubic estimate for 7 days after start"
)
plt.show()
实际应用中的注意事项
-
使用numpy.genfromtxt函数时,将缺失数据替换为-1并不总是问题的最佳解决方案。在某些情况下,用0替换可能更合适,但这并非通用解决方案。
-
numpy.genfromtxt函数有一个usemask参数。如果设置usemask=True,该函数会自动返回一个掩码数组,无需手动创建。
-
在处理真实数据时,通常需要仔细检查数据的特征和含义,以选择最合适的处理方法。掩码数组提供了一种灵活的方式来处理缺失或无效数据,同时保留原始数据的结构。
-
在进行数据分析和可视化时,掩码数组可以帮助我们更好地理解数据的质量和完整性。通过明确标记缺失或无效的数据点,我们可以避免在分析过程中引入偏差或错误。
-
当处理时间序列数据(如本例中的COVID-19数据)时,掩码数组特别有用。它们允许我们保留数据的时间结构,同时适当处理某些时间点的缺失数据。
-
在进行数据拟合或插值时,掩码数组可以帮助我们轻松地选择有效数据点进行计算,而无需修改原始数据结构。
-
对于大型数据集或需要频繁更新的数据,使用掩码数组可以提高代码的可维护性和可读性,因为它将数据处理逻辑与原始数据存储分离。