Объедините функциональность bfill () и ffill () - PullRequest
0 голосов
/ 12 декабря 2018

Допустим, у меня есть следующее pd.Series:

0     NaN
1     NaN
2     4.0
3     NaN
4     NaN
5     7.0
6     NaN
7     NaN
8     NaN
9     NaN
10    1.0
11    NaN
12    NaN
13    6.0
14    NaN
15    NaN

Я пытаюсь получить серию, в которой NaNs одинаково заполнены окружающими действительными образцами.Скажем, у меня есть N NaNs между двумя действительными выборками, я бы хотел, чтобы первый N/2 NaNs был заполнен последним допустимым образцом, а последний N/2 NaNs - следующимдостоверное наблюдение.В случае (N % 2) =! 0 дополнительное наблюдение может быть заполнено любым из двух окружающих, независимо от того, какое правило применяется, в порядке.Поэтому я хотел бы получить:

0     4
1     4
2     4
3     4
4     7
5     7
6     7
7     7
8     1
9     1
10    1
11    1
12    6
13    6
14    6
15    6

Ответы [ 3 ]

0 голосов
/ 12 декабря 2018
import pandas as pd
while pd.isnull(my_series).sum() > 0:
    my_series = my_series.fillna(method='ffill', limit=1).fillna(method='bfill', limit=1)

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

У меня никогда не было хорошей идеи сделать это (но я следил за этим, потому что это интересная проблема).Мне нравится другой ответ за сообразительность, но мне было любопытно, как он справился со скоростью.

def funcA(pd_series):
    m = pd_series.notna()
    c = m.cumsum()
    def f(x):
        lens = len(x.index)
        a = np.arange(lens)
        return a // (lens / 2) == 0
    mask = c[~m].groupby(c).transform(f)
    #should be removed
    #mask = mask.reindex(df.index, fill_value=False)
    return pd_series.where(mask, pd_series.bfill()).ffill().bfill()

def funcB(pd_series):
    while pd.isnull(pd_series).sum() > 0:
        pd_series = pd_series.fillna(method='ffill', limit=1).fillna(method='bfill', limit=1)
    return pd_series

ps = pd.Series(np.random.randint(0,10, size=(10000)))
ps[ps < 5] = np.nan

>>> import timeit
>>> timeit.timeit('funcA(ps)', setup='from __main__ import funcA, ps', number=100)
40.9788393480012
>>> timeit.timeit('funcB(ps)', setup='from __main__ import funcB, ps', number=100)
0.4896140840010048

Ну ... это не так хорошо, как я ожидал.Небольшая серия с половиной NaN может не быть хорошим тестом, так что, может быть, попробовать что-то, что должно замкнуть цикл while?

ps = pd.Series(np.random.randint(0,100, size=(1000000)))
ps[ps < 95] = np.nan

>>> timeit.timeit('funcA(ps)', setup='from __main__ import funcA, ps', number=10)
81.64654629600045
>>> timeit.timeit('funcB(ps)', setup='from __main__ import funcB, ps', number=10)
21.431495654000173

Ну, это как минимум ближе.Мне лень больше его масштабировать, но, похоже, вам понадобится 10 ^ 7 записей с 95% + NaN, прежде чем дополнительные издержки от маскировки и аранжировки окупятся.

0 голосов
/ 12 декабря 2018

Я нашел это интересной проблемой.Я довольно близко подошел к этому коду (более векторизованным / изумительным образом), может быть, один из более опытных людей мог бы помочь с последним шагом:

s = pd.Series([np.nan, np.nan, 4,np.nan, np.nan, 7, np.nan, np.nan, np.nan, np.nan, 1, np.nan, np.nan, 6, np.nan, np.nan])
df = pd.DataFrame(s)
df2 = df[df.isna().any(axis=1)]
grouped = df2.groupby((df2.index.to_series().diff() > 1).cumsum())
df3 = pd.DataFrame()
for group in grouped:
    test = group[1].reset_index()
    test['fill'] = pd.qcut(test.iloc[:,0], 2, labels=['ff', 'bf'])
    df3 = pd.concat([df3, test])
df3.set_index('index', inplace=True)
pd.merge(df, df3, how='left', left_index=True, right_index=True).drop(columns=['0_y'])

Вывод:

    0_x     fill

0   NaN     ff
1   NaN     bf
2   4.0     NaN
3   NaN     ff
4   NaN     bf
5   7.0     NaN
6   NaN     ff
7   NaN     ff
8   NaN     bf
9   NaN     bf
10  1.0     NaN
11  NaN     ff
12  NaN     bf
13  6.0     NaN
14  NaN     ff
15  NaN     bf
0 голосов
/ 12 декабря 2018

Идея состоит в том, чтобы создать логическую маску и фильтр bfill с where, затем ffill и последним bfill снова только для первых значений первой серии, если они начинаются с NaN:

m = df['A'].notna()
c = m.cumsum()

def f(x):
    lens = len(x.index)
    a = np.arange(lens)
    return a // (lens / 2) == 0

mask = c[~m].groupby(c).transform(f)
#should be removed
#mask = mask.reindex(df.index, fill_value=False)
df['B'] = df['A'].where(mask, df['A'].bfill()).ffill().bfill()
print (df)
      A    B
0   NaN  4.0
1   NaN  4.0
2   4.0  4.0
3   NaN  4.0
4   NaN  7.0
5   7.0  7.0
6   NaN  7.0
7   NaN  7.0
8   NaN  1.0
9   NaN  1.0
10  1.0  1.0
11  NaN  1.0
12  NaN  6.0
13  6.0  6.0
14  NaN  6.0
15  NaN  6.0
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...