Как обработать пропущенные значения в последовательностях определенной длины из кадра данных панд? - PullRequest
0 голосов
/ 27 апреля 2018

Суть:

Если столбец содержит последовательность из более чем, скажем, 5 пропущенных значений, я хотел бы удалить соответствующие индексы из этого кадра данных. Таким образом, в кадре данных, как показано ниже ...

                A       B
2017-01-01 -0.0053 -0.0062
2017-01-02     NaN  0.0016
2017-01-03     NaN  0.0043
2017-01-04     NaN -0.0077
2017-01-05     NaN -0.0070
2017-01-06     NaN  0.0058
2017-01-07  0.0024 -0.0074
2017-01-08  0.0018  0.0086
2017-01-09  0.0020  0.0012
2017-01-10 -0.0031 -0.0020
2017-01-11  0.0027     NaN
2017-01-12 -0.0050     NaN
2017-01-13 -0.0063     NaN
2017-01-14  0.0066  0.0095
2017-01-15  0.0039  0.0028

... Я хотел бы удалить индексы 2017-01-02 до 2017-01-06, чтобы желаемый результат выглядел так:

                 A       B
2017-01-01 -0.0053 -0.0062
2017-01-07  0.0024 -0.0074
2017-01-08  0.0018  0.0086
2017-01-09  0.0020  0.0012
2017-01-10 -0.0031 -0.0020
2017-01-11  0.0027     NaN
2017-01-12 -0.0050     NaN
2017-01-13 -0.0063     NaN
2017-01-14  0.0066  0.0095
2017-01-15  0.0039  0.0028

Как я могу сделать это эффективно?


Подробности:

Вот фрагмент кода для воспроизведения кадра данных:

# imports
import pandas as pd
import numpy as np
np.random.seed(1234)

# Reproducible data sample
def df_sample(rows, names):
    ''' Function to create data sample with random returns

    Parameters
    ==========
    rows : number of rows in the dataframe
    names: list of names to represent assets

    Example
    =======

    >>> returns(rows = 2, names = ['A', 'B'])

                  A       B
    2017-01-01  0.0027  0.0075
    2017-01-02 -0.0050 -0.0024
    '''
    listVars= names
    rng = pd.date_range('1/1/2017', periods=rows, freq='D')
    df_temp = pd.DataFrame(np.random.randint(-100,100,size=(rows, len(listVars))), columns=listVars) 
    df_temp = df_temp.set_index(rng)
    df_temp = df_temp / 10000

    return df_temp

df = df_sample(15,list('AB'))

Осложнения, которые я знаю

Если бы у фрейма данных были перекрывающиеся индексы с пропущенными значениями в нескольких столбцах, например:

                 A       B
2017-01-01 -0.0053 -0.0062
2017-01-02     NaN  0.0016
2017-01-03     NaN  0.0043
2017-01-04     NaN     NaN
2017-01-05     NaN     NaN
2017-01-06     NaN     NaN
2017-01-07  0.0024     NaN
2017-01-08  0.0018     NaN
2017-01-09  0.0020  0.0012
2017-01-10  NaN    -0.0020

... тогда, я думаю, любое решение, использующее apply столбец за столбцом, будет отображать временный кадр данных, подобный этому ...

                 A       B
2017-01-01 -0.0053 -0.0062
2017-01-07  0.0024     NaN
2017-01-08  0.0018     NaN
2017-01-09  0.0020  0.0012
2017-01-10  NaN    -0.0020

... и затем, возможно, игнорировать исходные отсутствующие индексы для column B с 2017-01-04 до 2017-01-08. Это, возможно, просто то, что нужно было бы принять, хотя. Но в идеале решение должно признать, что эти индексы изначально представляют 5 последовательно пропущенных значений, а также удалить эти индексы, чтобы результирующий кадр данных выглядел следующим образом:

                 A       B
2017-01-01 -0.0053 -0.0062
2017-01-09  0.0020  0.0012
2017-01-10  NaN    -0.0020

(А как насчет последнего NaN? Я бы просто fill forward. Но, сделав то же самое с каждым пропущенным значением, можно было бы уйти далеко.)

Так что я предполагаю, что это потенциально гораздо более сложная проблема, чем я изначально подозревал (и, возможно, это также является причиной того, что функция pandas.DataFrame.dropna не имеет конкретного аргумента для нее).


Что я пробовал:

1. pandas.DataFrame.dropna

Я думал, что аргумент thresh будет способом использовать pandas.DataFrame.dropna , но в соответствии с документами этот аргумент устанавливает порог для существующих вместо пропущено значения:

thresh: int, по умолчанию None

int value: требуется много значений, отличных от NA

2. Определение и поиск закономерностей столбца nan по столбцу

Ниже приведено возможное решение на основе предложенных ответов здесь . Тем не менее, вам необходимо определить, что вы ищете 5 и только 5 пропущенных значений в последовательности. Чтобы завершить решение, мне также нужно найти объединение индексов по всем спискам, которые представляют индексы отсутствующих последовательностей для всех столбцов, а затем установить подкадр данных, соответствующий этому.

Спасибо за любые другие предложения!

Вот все, что нужно для легкого копирования:

import pandas as pd
import numpy as np


np.random.seed(1234)

# Reproducible data sample
def df_sample(rows, names):
    ''' Function to create data sample with random returns

    Parameters
    ==========
    rows : number of rows in the dataframe
    names: list of names to represent assets

    Example
    =======

    >>> returns(rows = 2, names = ['A', 'B'])

                  A       B
    2017-01-01  0.0027  0.0075
    2017-01-02 -0.0050 -0.0024
    '''
    listVars= names
    rng = pd.date_range('1/1/2017', periods=rows, freq='D')
    df_temp = pd.DataFrame(np.random.randint(-100,100,size=(rows, len(listVars))), columns=listVars) 
    df_temp = df_temp.set_index(rng)
    df_temp = df_temp / 10000

    return df_temp

df = df_sample(15,list('AB'))

df['A'][1:6] = np.nan
df['B'][3:8] = np.nan
dfi = df

# convert to boolean values
df = dfi
df = df.isnull()

# specify pattern
pattern = [True,True, True, True, True]

# prepare for a for loop
idx = []

# loop through all columns and identify sequence of missing values
for col in df:
    df_temp = df[col].to_frame()

    matched = df_temp.rolling(len(pattern)).apply(lambda x: all(np.equal(x, pattern)))
    matched = matched.sum(axis = 1).astype(bool)
    idx_matched = np.where(matched)[0]
    subset = [range(match-len(pattern)+1, match+1) for match in idx_matched]

    result = pd.concat([df.iloc[subs,:] for subs in subset], axis = 0).index
    idx.append(result)
print(idx)

Вывод (индексы для последовательностей nan, столбец за столбцом):

    [DatetimeIndex(['2017-01-02', '2017-01-03', '2017-01-04', '2017-01-05','2017-01-06'],
          dtype='datetime64[ns]', freq=None),
    DatetimeIndex(['2017-01-04', '2017-01-05', '2017-01-06', '2017-01-07', '2017-01-08'],
          dtype='datetime64[ns]', freq=None)]

1 Ответ

0 голосов
/ 27 апреля 2018

Это должно решить это за вас. Он не удаляет строки до конца, поэтому он правильно разрешит несколько столбцов, как вы хотите во втором сценарии. Я использовал df из вашего раздела осложнений для вывода приведенного ниже кода.

Пояснение:

  • Мы создаем еще одну df, в которой NaN значениям присваивается ноль, а каждому конечному значению присваивается 1 (Если ваши начальные df имеют нулевые значения, вам необходимо сначала сопоставить их с любым другим числом в этот манекен df2, затем .fillna(0).astype('bool'))

  • Группировка по кумулятивной сумме каждого столбца позволяет нам найти более 5 последовательных значений NaN. Сравнение с исходным df затем гарантирует, что мы не фиксируем первое ненулевое значение.

  • Маска создается в конце для любой строки, которая должна быть отброшена, поэтому вы правильно разрешаете ее для нескольких столбцов с перекрывающимися значениями NaN.

Вот код:

import pandas as pd
import numpy as np

## If the initial df contains values of 0 do this instead of the first line below
#df2 = df.copy()
#df2[df2==0] = 0.01
#df2 = df2.fillna(0).astype('bool').cumsum()

# Min number of consecutive NaN values to begin dropping
n_cons = 5

df2 = df.fillna(0).astype('bool').cumsum()
for col in df2.columns:
    df2[col] = df2.groupby(col)[col].transform(lambda x: np.size(x) > n_cons)
    df2[col] = df2[col] & df[col].isnull()

mask = df2.any(axis=1)

df[~mask]
#                 A       B
#2017-01-01 -0.0053 -0.0062
#2017-01-09  0.0020  0.0012
#2017-01-10     NaN -0.0020
...