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)