Make pandas DataFrame в dict и dropna
У меня есть некоторый pandas DataFrame с NaN в нем.
Вот так:
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
Теперь я хочу изгнать из него и в то же время удалить NaNs.
Результат должен выглядеть следующим образом:
{'A': {1: 2, 2: 3, 3: 4}, 'B': {2: 44.0}}
Но использование функции pandas to_dict дает мне такой результат:
>>> data.to_dict()
{'A': {1: 2, 2: 3, 3: 4}, 'B': {1: nan, 2: 44.0, 3: nan}}
Итак, как сделать dict из DataFrame и избавиться от NaNs?
Ответы
Ответ 1
напишите функцию, содержащуюся to_dict, из pandas
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)
и в результате вы получите то, что хотите:
>>> dict
{'A': {1: 2, 2: 3, 3: 4}, 'B': {2: 44.0}}
Ответ 2
Есть много способов добиться этого, я потратил некоторое время на оценку производительности на не очень большой (70 тыс.) фреймворке. Хотя ответ @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
Другой медленный метод:
%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
Самый быстрый метод, который я мог найти:
%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
Формат этого вывода - это словарь, ориентированный на ряд строк. Возможно, вам потребуется внести коррективы, если вы хотите, чтобы в вопросе была нужна форма столбца.
Очень интересно, если кто-нибудь найдет еще более быстрый ответ на этот вопрос.
Ответ 3
Вы можете использовать диктовку и обходить столбцы
{col:df[col].dropna().to_dict() for col in df}
Ответ 4
Я написал функцию для решения этой проблемы, не переопределяя to_dict и не вызывая ее более одного раза. Подход состоит в том, чтобы рекурсивно обрезать "листья" с помощью значения nan/None.
def trim_nan_leaf(tree):
"""For a tree of dict-like and list-like containers, prune None and NaN leaves.
Particularly applicable for json-like dictionary objects
"""
# d may be a dictionary, iterable, or other (element)
# * Do not recursively iterate if string
# * element is the base case
# * Only remove nan and None leaves
def valid_leaf(leaf):
if leaf is None:
return(False)
if isinstance(leaf, numbers.Number):
if (not math.isnan(leaf)):
return(leaf != -9223372036854775808)
return(False)
return(True)
# Attempt dictionary
try:
return({k: trim_nan_leaf(tree[k]) for k in tree.keys() if valid_leaf(tree[k])})
except AttributeError:
# Execute base case on string for simplicity...
if isinstance(tree, str):
return(tree)
# Attempt iterator
try:
# Avoid infinite recursion for self-referential objects (like one-length strings!)
if tree[0] == tree:
return(tree)
return([trim_nan_leaf(leaf) for leaf in tree if valid_leaf(leaf)])
# TypeError occurs when either [] or iterator are availble
except TypeError:
# Base Case
return(tree)