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

时间:2018-09-04 17:02:15

标签: python pandas dataframe

摘要

在许多科学应用中,重要的是要跟踪不同类型的缺失值。是因为该人没有工作,还是因为他们有工作但拒绝回答,所以缺少“主要工作的每周收入”的值吗?

  • 将所有缺少的值存储为NANaN会丢失此信息。
  • 单独的列中存储缺失的值标签(例如,“因为没有工作而遗漏”,“由于拒绝回答而遗漏”),这意味着研究人员在执行每项操作时都必须跟踪两列–例如groupby,重命名等。这为错误和错误创造了无穷的机会。
  • 同一列中存储缺失值标签(例如,如下面的示例所示为负数,或99999等非常大的数字)意味着研究人员必须手动跟踪缺失值标签的方式会为每列进行编码,并为错误创造许多其他机会(例如,忘记一列包含缺少的值,并采用原始均值而不是使用正确的掩码)。

通过使用既存储数值又包含缺失值标签的数据类型以及知道如何处理此数据类型的函数,可以很容易地在Stata中解决此问题(请参见下文)。这是非常高性能(数据类型仍然是数字,而不是字符串或混合的–考虑NumPy的数据类型,除了拥有NaN,{{1} }等)在熊猫中实现这种目标的最佳方法是什么?

注意:我是经济学家,但是对于政治学家,流行病学家等来说,这也是一个非常普通的工作流程-涉及调查数据的任何人。在这种情况下,分析人员通过密码本知道缺失的值是什么,真的很在意跟踪它们,并且有数百或数千列要处理–因此,确实需要一种自动的方法来跟踪它们。 / p>

动机/背景

在处理任何类型的调查数据时会遇到多种丢失数据的情况非常普遍。

以下是用于生成官方就业统计数据的政府调查表中的一个最小示例:

  • [Q1]你有工作吗?
  • [Q2] [如果Q1 =是]您从该工作获得的每周收入是多少?

在全球几乎所有政府进行的劳动力调查中(例如UK Labour Force Survey,美国当前人口调查等),上述情况都会发生。

现在,对于给定的受访者,如果缺少[Q2],则可能是(1)他们对[Q1]的回答是“否”,因此,不合格被要求[Q2],或那个(2)他们对[Q1]的回答是“是”,但拒绝回答了[Q2](也许是因为他们为自己挣多少钱而感到尴尬,或者是因为他们不知道)。 / p>

作为一名研究人员,对我来说至关重要的是发生的是(1)还是发生的(2)。假设我的工作是报告美国工人的平均每周收入。如果此[Q2]列有很多缺失值,但都标记为“缺失,因为受访者对[Q1]的回答为否”,那么我可以放心地将[Q2]的平均值作为平均值–实际上,是平均值工作人员的每周收入。 (所有缺少的价值观是没有工作的人。)

另一方面,如果那些[Q2]缺失值都标记为“缺少,因为受访者被问了这个问题但拒绝回答”,那么我不能简单地将[Q2]的平均值报告为工人的平均每周收入。我需要对结果发表警告。我需要分析不回答的人的类型(他们是随机失踪的人,还是收入较高的职业的人更有可能拒绝,例如,偏向我的结果?)。可能我会尝试估算缺少的值,依此类推。

问题

由于这些“失踪原因”非常重要,因此政府统计机构将在此列中编码不同的原因:

enter image description here

因此,包含上述[Q2]答案的列可能包含值[1500,-8、10000,-2、3000,-1、6400]。

在这种情况下,[Q2]的答案是'1500','10000'等(每周收入$ 1,500,每周收入$ 10,000等);而“ -8”表示他们没有资格回答(因为他们回答[Q1]否)。“-2”表示他们有资格回答但拒绝回答,依此类推。

现在,很显然,如果我将本专栏的平均值作为平均值,那我将得到毫无意义的东西。

另一方面,如果我只用NaN1替换所有负值,那么我可以取平均值–但是我已经丢失了所有有关为什么缺少值的有价值的信息。例如,我可能想拥有一个函数,该函数可以接收任何列并报告该列的均值和中位数之类的统计信息,合格观测值的数量(即value = -8以外的所有值)以及不丢失的那些的百分比。

在Stata中效果很好

NaN2中执行此操作非常容易。 NaN缺少27个数字类别:“。a”至“ .z”。 (更多详细信息here。)我可以写:

Stata

以此类推。

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

Stata

报告平均值时,replace weekly_income = .a if weekly_income == -1 replace weekly_income = .b if weekly_income == -8 将自动忽略编码为缺失的值(实际上,它们现在不是数字);但它也只会为我提供我所关注的观察结果的缺失值统计(在这种情况下,是那些有资格被问到该问题的人,即那些最初未编码为“ -8”的人)

在熊猫中处理此问题的最佳方法是什么?

设置:

stats weekly_income if weekly_income!=.b

所需结果:

Stata

“显而易见”的解决方法

很明显,一种选择是将每一列分为两列:一个数字列具有不丢失的值和NaN,而另一列是分类列,其类别用于不同类型的缺失值。

但这非常不方便。这些调查通常有成千上万的专栏,研究人员很可能在某些类型的经济分析中使用数百种。每个“基础”列都有两列,这意味着研究人员必须跟踪她执行的每项操作的两列,例如groupby,重命名等。这为错误和错误创造了无尽的机会。这也意味着显示表非常浪费–对于任何列,我现在都需要显示两个列,对于任何给定的观察结果,其中之一总是多余的。 (这既浪费了屏幕空间,也浪费了人类分析人员的注意力,因为它们必须确定哪两列是“对”。)

其他想法

我想到的另外两种想法,可能都不是理想的:

(1)在pandas中创建一个新数据类型,其工作方式与Stata类似(即,在数字列的允许值中添加“ .a”,“。b”等)。

(2)使用上面的两列解决方案,但是(重新)在pandas中编写“包装器”函数,以便“ groupby”等为我跟踪列对。

我怀疑(1)从长远来看是最好的解决方案,但它可能需要大量开发。

另一方面,也许已经有解决这个问题的软件包?还是人们有更好的解决方法?

2 个答案:

答案 0 :(得分:1)

为展示解决方案,我自由地更改了missing_dict键以匹配income的数据类型。

>>> 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

请注意过滤器值周围的多余括号:我们必须将tuple的值传递给isin。然后应用 tilde 运算符(按位取反)获得一系列布尔值。

最后,将mean应用于结果数据列:

>>> df[(~df.income.isin((df.income.missing_dict)))].mean()
income    5225.0
dtype: float64

这会朝正确的方向抛吗?在这里,您可以根据需要简单地用适当的列或变量名称替换income

答案 1 :(得分:0)

Pandas最近引入了一个名为ExtensionArray的自定义数组类型,该类型允许定义实质上是一个自定义列类型的字段,使您可以(在某种程度上)使用实际值以及丢失的数据,而无需处理两列。这是一个非常非常粗糙的实现,几乎没有经过测试:

[[[255,   0,   0],
  [255,   0,   0],
  [255,   0,   0],
  ...,
  [  8,  20,   8],
  [ 12,  15,  20],
  [ 16,  14,  26]],

  [[255,   0,   0],
  [ 37,  27,  20],
  [ 45,  36,  32],
  ...,
  [177, 187, 157],
  [180, 192, 164],
  [182, 193, 167]]]

使用此实现,您可以执行以下操作:

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)

希望了解此API的人可以听到并帮助改善它。对于初学者,>>> 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]) 不能在a中使用,只能在DataFrames中使用。

Series