Python 如何跟踪熊猫中不同类型的缺失值? 总结

Python 如何跟踪熊猫中不同类型的缺失值? 总结,python,pandas,dataframe,Python,Pandas,Dataframe,在许多科学应用中,跟踪不同种类的缺失值是很重要的。“每周主要工作收入”的值是否因为该人没有工作,或者因为他们有工作但拒绝回答而丢失 将所有缺少的值存储为NA或NaN将丢失此信息 在单独的列中存储缺少的值标签(例如,“因为没有工作而缺少”、“因为拒绝回答而缺少”)意味着研究人员必须跟踪她执行的每个操作的两列,例如分组、重命名等。这为错误和错误创造了无限的机会 将缺失值标签存储在同一列中(例如,作为负数,如下图所示,或非常大的数字,如99999),意味着研究人员必须手动跟踪每列缺失值标签的编码方式

在许多科学应用中,跟踪不同种类的缺失值是很重要的。“每周主要工作收入”的值是否因为该人没有工作,或者因为他们有工作但拒绝回答而丢失

  • 将所有缺少的值存储为
    NA
    NaN
    将丢失此信息
  • 单独的列中存储缺少的值标签(例如,“因为没有工作而缺少”、“因为拒绝回答而缺少”)意味着研究人员必须跟踪她执行的每个操作的两列,例如分组、重命名等。这为错误和错误创造了无限的机会
  • 将缺失值标签存储在同一列中(例如,作为负数,如下图所示,或非常大的数字,如99999),意味着研究人员必须手动跟踪每列缺失值标签的编码方式,并创造许多其他错误机会(例如,忘记列包含缺少的值,并采用原始平均值,而不是使用正确的掩码)
Stata
(见下文)中,通过使用存储数值和缺失值标签的数据类型,以及知道如何处理此数据类型的函数,可以非常容易地处理此问题。这是一种非常高的性能(数据类型仍然是数字的,而不是字符串或混合的–想想NumPy的数据类型,除了我们有
NaN1
NaN2
,等等之外)在熊猫中实现这一点的最佳方法是什么

注:我是一名经济学家,但这也是政治科学家、流行病学家等——任何处理调查数据的人——的一个非常常见的工作流程。在这种情况下,分析师通过代码簿知道缺失的值是什么,真正关心跟踪它们,并且有数百或数千列需要处理——因此,indeed、 需要一种自动跟踪它们的方法

动机/背景 在处理任何类型的调查数据时,有多种类型的缺失数据是非常常见的

以下是用于编制官方就业统计数据的政府调查问卷中的一个简单示例:

  • [Q1]你有工作吗
  • [Q2][如果Q1=Yes]你的每周收入是多少
世界上几乎每一次政府运营的劳动力调查(如美国、美国当前人口调查等)都会出现上述情况

现在,对于给定的受访者来说,如果[Q2]缺失,可能是(1)他们对[Q1]的回答是否定的,因此没有资格被问[Q2],或者(2)他们对[Q1]的回答是肯定的,但拒绝回答[Q2](可能是因为他们对自己挣多少感到尴尬,或者因为他们不知道)

作为一名研究人员,这对我来说非常重要,是(1)发生了,还是(2)发生了。假设我的工作是报告美国工人的平均周收入。如果[Q2]栏中有许多缺失值,但它们都被标记为“缺失”,因为受访者对[Q1]的回答是否定的’,然后我可以自信地计算[Q2]的平均值——它实际上是工作人员的平均周收入(所有缺失的值都是没有工作的人)

另一方面,如果这些[Q2]缺失值都被标记为“因为受访者被问到这个问题但拒绝回答而缺失”,那么我不能简单地将[Q2]的平均值报告为工人的平均周收入。我需要对我的结果提出警告。我需要分析那些没有回答的人(他们是随机缺失的,还是高收入职业的人更可能拒绝,例如,对我的结果产生偏见?)

问题 由于这些“失踪原因”非常重要,政府统计机构将在栏中对不同原因进行编码:

因此,包含上述[Q2]答案的列可能包含值[1500,-81000,-23000,-1640]

在本例中,“1500”、“10000”等是[Q2]的“真实”答案(1500美元的周收入、10000美元的周收入等);而“-8”表示他们没有资格回答(因为他们对[Q1]的回答是否定的),-2”表示他们有资格回答,但拒绝回答,依此类推

现在,很明显,如果我取这个专栏的平均值,我会得到一些毫无意义的东西

另一方面,如果我只是将所有负值替换为
NaN
,那么我可以取平均值,但我已经丢失了所有关于值丢失原因的有价值信息。例如,我可能希望有一个函数,它可以取任何列和报告,对于该列,统计数据如平均值和中值,eligib的数量le观察值(即除值=-8外的所有内容),以及未缺失的百分比

它在斯塔塔非常有效 在
Stata
中执行此操作非常简单。
Stata
有27个数字缺失类别:'.a'到'.z'。(更多详细信息)。我可以写:

如果周收入=-1,则替换周收入=.a
如果周收入==-8,则替换周收入=.b

等等

然后(用伪代码)我就可以写了

统计每周收入如果每周收入!=.b

报告平均值时,
Stata
将自动忽略编码为缺失的值(事实上,它们现在不是数字);但它也将为我提供缺失值统计数据,仅针对我关心的观察值(在这种情况下,那些有资格被问到问题的人,即那些最初没有编码'-8')

在熊猫身上处理这个问题的最好方法是什么? 设置:

>>> import pandas as pd
>>> df = pd.DataFrame.from_dict({
        'income': [1500, -8, 10000, -2, 3000, -1, 6400]})
预期结果:

>>> df.income.missing_dict = {'-1': ['.a', 'Don\'t know'], '-2': ['.b', 'Refused']} # etc.
>>> df
  income
0       1500
1  Inapplic.
2      10000
3    Refused
4       3000
5 Don't know
6       6400

>>> assert df.income.mean() == np.mean([1500, 10000, 3000, 6400])
(passes)
“显而易见”的工作
>>> df
   income
0    1500
1      -8
2   10000
3      -2
4    3000
5      -1
6    6400
>>> df.income.missing_dict
{-8: ['.c', 'Stifled by companion'], -2: ['.b', 'Refused'], -1: ['.a', "Don't know"]}
>>> df[(~df.income.isin((df.income.missing_dict)))]
   income
0    1500
2   10000
4    3000
6    6400
>>> df[(~df.income.isin((df.income.missing_dict)))].mean()
income    5225.0
dtype: float64
import numpy as np
import pandas as pd
from pandas.core.arrays.base import ExtensionArray


class StataData(ExtensionArray):
    def __init__(
        self, data, missing=None, factors=None, dtype=None, copy=False
    ):
        def own(array, dtype=dtype):
            array = np.asarray(array, dtype)
            if copy:
                array = array.copy()
            return array

        self.data = own(data)

        if missing is None:
            missing = np.zeros_like(data, dtype=int)
        else:
            missing = own(missing, dtype=int)
        self.missing = missing

        self.factors = own(factors)

    @classmethod
    def _from_sequence(cls, scalars, dtype=None, copy=False):
        return cls(scalars, dtype=dtype, copy=copy)

    @classmethod
    def _from_factorized(cls, data, original):
        return cls(original, None, data)

    def __getitem__(self, key):
        return type(self)(
            self.data[key], self.missing[key], self.factors
        )

    def __setitem__(self, key, value):
        self.data[key] = value
        self.missing[key] = 0

    def __len__(self):
        return len(self.data)

    def __iter__(self):
        return iter(self.data)

    @property
    def dtype(self):
        return self.data.dtype

    @property
    def shape(self):
        return self.data.shape

    @property
    def nbytes(self):
        return self.data.nbytes + self.missing.nbytes + self.factors.nbytes

    def view(self):
        return self

    @property
    def reason_missing(self):
        return self.missing

    def isna(self):
        return self.missing != 0

    def __repr__(self):
        s = {}
        for attr in ['data', 'missing', 'factors']:
            s[attr] = getattr(self, attr)
        return repr(s)
>>> a = StataData([1, 2, 3, 4], [0, 0, 1, 0])
>>> s = pd.Series(a)
>>> print(s[s.isna()])
2    3
dtype: int32
>>> print(s[~s.isna()])
0    1
1    2
3    4
dtype: int32
>>> print(s.isna().values.reason_missing)
array([1])
>>> print(pd.DataFrame({'a': s}).isna())
0  False
1  False
2  False
3  False