将pandas DataFrame制作成dict和dropna

der*_*ojo 14 python pandas

我有一些带有NaNs的pandas DataFrame.像这样:

import pandas as pd
import numpy as np
raw_data={'A':{1:2,2:3,3:4},'B':{1:np.nan,2:44,3:np.nan}}
data=pd.DataFrame(raw_data)
>>> data
   A   B
1  2 NaN
2  3  44
3  4 NaN
Run Code Online (Sandbox Code Playgroud)

现在我想用它制作一个字典,同时删除NaN.结果应如下所示:

{'A': {1: 2, 2: 3, 3: 4}, 'B': {2: 44.0}}
Run Code Online (Sandbox Code Playgroud)

但是使用pandas to_dict函数给我一个这样的结果:

>>> data.to_dict()
{'A': {1: 2, 2: 3, 3: 4}, 'B': {1: nan, 2: 44.0, 3: nan}} 
Run Code Online (Sandbox Code Playgroud)

那么如何从DataFrame中制作一个字典并摆脱NaN?

jez*_*ael 14

第一个图为每列生成字典,因此输出很少是很长的字典,字典的数量取决于列数。

我测试用多种方法perfplot和最快的方法是通过循环的每一列和删除缺失值或None由sSeries.dropna或与Series.notnaboolean indexing较大DataFrames。

较小的 DataFrames 是最快的字典理解,通过NaN != NaN技巧测试缺失值并测试Nones。

图形

np.random.seed(2020)
import perfplot

def comp_notnull(df1):
    return {k1: {k:v for k,v in v1.items() if pd.notnull(v)} for k1, v1 in df1.to_dict().items()}

def comp_NaNnotNaN_None(df1):
    return {k1: {k:v for k,v in v1.items() if v == v and v is not None} for k1, v1 in df1.to_dict().items()}

def comp_dropna(df1):
    return {k: v.dropna().to_dict() for k,v in df1.items()}

def comp_bool_indexing(df1):
    return {k: v[v.notna()].to_dict() for k,v in df1.items()}

def make_df(n):
    df1 = pd.DataFrame(np.random.choice([1,2, np.nan], size=(n, 5)), columns=list('ABCDE'))
    return df1

perfplot.show(
    setup=make_df,
    kernels=[comp_dropna, comp_bool_indexing, comp_notnull, comp_NaNnotNaN_None],
    n_range=[10**k for k in range(1, 7)],
    logx=True,
    logy=True,
    equality_check=False,
    xlabel='len(df)')
Run Code Online (Sandbox Code Playgroud)

另一种情况是,如果每行生成字典 - 获取大量小字典的列表,那么最快的是过滤 NaN 和 None 的列表理解:

g1

np.random.seed(2020)
import perfplot


def comp_notnull1(df1):
    return [{k:v for k,v in m.items() if pd.notnull(v)} for m in df1.to_dict(orient='r')]

def comp_NaNnotNaN_None1(df1):
    return [{k:v for k,v in m.items() if v == v and v is not None} for m in df1.to_dict(orient='r')]

def comp_dropna1(df1):
    return [v.dropna().to_dict() for k,v in df1.T.items()]

def comp_bool_indexing1(df1):
    return [v[v.notna()].to_dict() for k,v in df1.T.items()]


def make_df(n):
    df1 = pd.DataFrame(np.random.choice([1,2, np.nan], size=(n, 5)), columns=list('ABCDE'))
    return df1

perfplot.show(
    setup=make_df,
    kernels=[comp_dropna1, comp_bool_indexing1, comp_notnull1, comp_NaNnotNaN_None1],
    n_range=[10**k for k in range(1, 7)],
    logx=True,
    logy=True,
    equality_check=False,
    xlabel='len(df)')
Run Code Online (Sandbox Code Playgroud)


rus*_*ro1 8

你可以拥有自己的映射类,在其中你可以摆脱 NAN:

class NotNanDict(dict):

    @staticmethod
    def is_nan(v):
        if isinstance(v, dict):
            return False
        return np.isnan(v)

    def __new__(self, a):
        return {k: v for k, v in a if not self.is_nan(v)} 

data.to_dict(into=NotNanDict)
Run Code Online (Sandbox Code Playgroud)

输出:

{'A': {1: 2, 2: 3, 3: 4}, 'B': {2: 44.0}}
Run Code Online (Sandbox Code Playgroud)

时间安排(来自@jezrael的回答):

在此输入图像描述

要提高速度,您可以使用numba

from numba import jit

@jit
def dropna(arr):
    return [(i + 1, n) for i, n in enumerate(arr) if not np.isnan(n)]


class NotNanDict(dict):

    def __new__(self, a):
        return {k: dict(dropna(v.to_numpy())) for k, v in a}

data.to_dict(orient='s', into=NotNanDict)
Run Code Online (Sandbox Code Playgroud)

输出:

{'A': {1: 2, 2: 3, 3: 4}, 'B': {2: 44.0}}
Run Code Online (Sandbox Code Playgroud)

时间安排(来自@jezrael的回答):

在此输入图像描述

  • @RicksupportsMonica 是的。你是对的。这个比较慢。 (2认同)
  • 很遗憾,因为我认为这是最具可读性的方法。 (2认同)

Pet*_*ien 7

有很多方法可以实现这一点,我花了一些时间来评估一个不太大(70k)的数据帧上的性能.虽然@ der_die_das_jojo的答案是有用的,但它也很慢.

所建议的回答这个问题其实原来是约5倍上的大数据帧更快.

在我的测试数据帧(df)上:

以上方法:

%time [ v.dropna().to_dict() for k,v in df.iterrows() ]
CPU times: user 51.2 s, sys: 0 ns, total: 51.2 s
Wall time: 50.9 s
Run Code Online (Sandbox Code Playgroud)

另一个慢的方法:

%time df.apply(lambda x: [x.dropna()], axis=1).to_dict(orient='rows')
CPU times: user 1min 8s, sys: 880 ms, total: 1min 8s
Wall time: 1min 8s
Run Code Online (Sandbox Code Playgroud)

我能找到最快的方法:

%time [ {k:v for k,v in m.items() if pd.notnull(v)} for m in df.to_dict(orient='rows')]
CPU times: user 14.5 s, sys: 176 ms, total: 14.7 s
Wall time: 14.7 s
Run Code Online (Sandbox Code Playgroud)

此输出的格式是面向行的字典,如果您想在问题中使用面向列的表单,则可能需要进行调整.

如果有人找到这个问题更快的答案,那么非常感兴趣.


der*_*ojo 6

写一个来自pandas的to_dict的函数

import pandas as pd
import numpy as np
from pandas import compat 

def to_dict_dropna(self,data):
  return dict((k, v.dropna().to_dict()) for k, v in compat.iteritems(data))

raw_data={'A':{1:2,2:3,3:4},'B':{1:np.nan,2:44,3:np.nan}}
data=pd.DataFrame(raw_data)

dict=to_dict_dropna(data)
Run Code Online (Sandbox Code Playgroud)

结果你得到了你想要的东西:

>>> dict
{'A': {1: 2, 2: 3, 3: 4}, 'B': {2: 44.0}}
Run Code Online (Sandbox Code Playgroud)


vil*_*oro 6

有很多方法可以解决这个问题。根据行数,最快的方法将会改变。由于性能相关,我知道行数很大。

import pandas as pd
import numpy as np

# Create a dataframe with random data
df = pd.DataFrame(np.random.randint(10, size=[1_000_000, 2]), columns=["A", "B"])

# Add some NaNs
df.loc[df["A"]==1, "B"] = np.nan
Run Code Online (Sandbox Code Playgroud)

我得到的最快的解决方案是简单地使用该dropna方法和字典理解:

%time {col: df[col].dropna().to_dict() for col in df.columns}

CPU times: user 528 ms, sys: 87.2 ms, total: 615 ms
Wall time: 615 ms
Run Code Online (Sandbox Code Playgroud)

与建议的解决方案之一相比,速度快 10 倍:

现在,如果我们使用建议的解决方案之一对其进行测试,我们会得到:

%time [{k:v for k,v in m.items() if pd.notnull(v)} for m in df.to_dict(orient='rows')]

CPU times: user 5.49 s, sys: 205 ms, total: 5.7 s
Wall time: 5.69 s
Run Code Online (Sandbox Code Playgroud)

它也比其他选项快 2 倍,例如:

%time {k1: {k:v for k,v in v1.items() if v == v and v is not None} for k1, v1 in df.to_dict().items()}

CPU times: user 900 ms, sys: 133 ms, total: 1.03 s
Wall time: 1.03 s
Run Code Online (Sandbox Code Playgroud)

这个想法是始终尝试使用pandasnumpy内置函数,因为它们比常规 python 更快。


小智 5

您可以使用字典理解并遍历列

{col:df[col].dropna().to_dict() for col in df}
Run Code Online (Sandbox Code Playgroud)