我有一些带有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.notna在boolean 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 的列表理解:
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)
你可以拥有自己的映射类,在其中你可以摆脱 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的回答):
有很多方法可以实现这一点,我花了一些时间来评估一个不太大(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)
此输出的格式是面向行的字典,如果您想在问题中使用面向列的表单,则可能需要进行调整.
如果有人找到这个问题更快的答案,那么非常感兴趣.
写一个来自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)
有很多方法可以解决这个问题。根据行数,最快的方法将会改变。由于性能相关,我知道行数很大。
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)
这个想法是始终尝试使用pandas或numpy内置函数,因为它们比常规 python 更快。
小智 5
您可以使用字典理解并遍历列
{col:df[col].dropna().to_dict() for col in df}
Run Code Online (Sandbox Code Playgroud)