Znajdź kolejne zera w DataFrame i wykonaj zamianę warunkową

10

Mam taki zestaw danych:

Przykładowa ramka danych

import pandas as pd

df = pd.DataFrame({
    'names': ['A','B','C','D','E','F','G','H','I','J','K','L'],
    'col1': [0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0],
    'col2': [0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0]})

Chciałbym zastąpić część 0jest w col1i col2z 1jest, ale nie zastępują 0JEŻELI trzy lub więcej 0'S są sąsiednie w tej samej kolumnie. Jak można to zrobić za pomocą pand?

Oryginalny zestaw danych:

names   col1    col2
A   0   0
B   1   0
C   0   0
D   1   0
E   1   1
F   1   0
G   0   1
H   0   0
I   0   1
J   1   0
K   0   0
L   0   0

Pożądany zestaw danych:

names   col1    col2
A   1   0
B   1   0
C   1   0
D   1   0
E   1   1
F   1   1
G   0   1
H   0   1
I   0   1
J   1   0
K   1   0
L   1   0
Kevin
źródło
co col2?
oW_
df.loc[(df['col1']+df['col1'].shift(1)+df['col1'].shift(2)>0)&(df['col1']+df['col1'].shift(1)+df['col1'].shift(-1)>0)&(df['col1']+df['col1'].shift(-1)+df['col1'].shift(-2)>0)]=1 pozostawia to jednak nietknięte pierwsze i ostatnie dwa rzędy
oW_

Odpowiedzi:

9

Rozważ następujące podejście:

def f(col, threshold=3):
    mask = col.groupby((col != col.shift()).cumsum()).transform('count').lt(threshold)
    mask &= col.eq(0)
    col.update(col.loc[mask].replace(0,1))
    return col

In [79]: df.apply(f, threshold=3)
Out[79]:
       col1  col2
names
A         1     0
B         1     0
C         1     0
D         1     0
E         1     1
F         1     1
G         0     1
H         0     1
I         0     1
J         1     0
K         1     0
L         1     0

Krok po kroku:

In [84]: col = df['col2']

In [85]: col
Out[85]:
names
A    0
B    0
C    0
D    0
E    1
F    0
G    1
H    0
I    1
J    0
K    0
L    0
Name: col2, dtype: int64

In [86]: (col != col.shift()).cumsum()
Out[86]:
names
A    1
B    1
C    1
D    1
E    2
F    3
G    4
H    5
I    6
J    7
K    7
L    7
Name: col2, dtype: int32

In [87]: col.groupby((col != col.shift()).cumsum()).transform('count')
Out[87]:
names
A    4
B    4
C    4
D    4
E    1
F    1
G    1
H    1
I    1
J    3
K    3
L    3
Name: col2, dtype: int64

In [88]: col.groupby((col != col.shift()).cumsum()).transform('count').lt(3)
Out[88]:
names
A    False
B    False
C    False
D    False
E     True
F     True
G     True
H     True
I     True
J    False
K    False
L    False
Name: col2, dtype: bool

In [89]: col.groupby((col != col.shift()).cumsum()).transform('count').lt(3) & col.eq(0)
Out[89]:
names
A    False
B    False
C    False
D    False
E    False
F     True
G    False
H     True
I    False
J    False
K    False
L    False
Name: col2, dtype: bool
MaxU
źródło
Oprócz wyjaśnienia col.groupby((col != col.shift()).cumsum()). uwaga: groupby(by, ...)tutaj bymoże być dykt lub Seria, po przekazaniu dykta lub Serii, Serie lub WARTOŚCI dykt zostaną użyte do ustalenia grup.
Mithril
5

Powinieneś użyć, pandas.DataFrame.shift()aby znaleźć potrzebny wzór.

Kod:

def fill_zero_not_3(series):
    zeros = (True, True, True)
    runs = [tuple(x == 0 for x in r)
            for r in zip(*(series.shift(i)
                           for i in (-2, -1, 0, 1, 2)))]
    need_fill = [(r[0:3] != zeros and r[1:4] != zeros and r[2:5] != zeros)
                 for r in runs]
    retval = series.copy()
    retval[need_fill] = 1
    return retval

Kod testowy:

import pandas as pd

df = pd.DataFrame({
    'names': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'],
    'col1': [0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0],
    'col2': [0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0]}).set_index('names')

df['col1'] = fill_zero_not_3(df['col1'])
df['col2'] = fill_zero_not_3(df['col2'])
print(df)

Wyniki:

       col1  col2
names            
A         1     0
B         1     0
C         1     0
D         1     0
E         1     1
F         1     1
G         0     1
H         0     1
I         0     1
J         1     0
K         1     0
L         1     0
Stephen Rauch
źródło
Myślę, że mam szybszy sposób niż twój.
Kevin,
2

Odpowiedź Stephena Raucha jest bardzo mądra, ale powolna, gdy zastosowałem ją do dużego zestawu danych. Zainspirowany tym postem , myślę, że mam bardziej skuteczny sposób na osiągnięcie tego samego celu.

Kod:

import pandas as pd

df = pd.DataFrame({
    'names': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'],
    'col1': [0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0],
    'col2': [0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0]}).set_index('names')

for i in range(df.shape[1]):
    iszero = np.concatenate(([0], np.equal(df.iloc[:, i].values, 0).view(np.int8), [0]))
    absdiff = np.abs(np.diff(iszero))
    zerorange = np.where(absdiff == 1)[0].reshape(-1, 2)
    for j in range(len(zerorange)):
        if zerorange[j][1] - zerorange[j][0] < 3:
            df.iloc[zerorange[j][0]:zerorange[j][1], i] = 1
print(df)

Wyniki:

        col1  col2
names            
A         1     0
B         1     0
C         1     0
D         1     0
E         1     1
F         1     1
G         0     1
H         0     1
I         0     1
J         1     0
K         1     0
L         1     0
Kevin
źródło