Zastępowanie pustych wartości (spacji) NaN w pandach

150

Chcę znaleźć wszystkie wartości w ramce danych Pandas, które zawierają białe znaki (dowolną ilość) i zastąpić te wartości NaN.

Jakieś pomysły, jak można to poprawić?

Zasadniczo chcę to zmienić:

                   A    B    C
2000-01-01 -0.532681  foo    0
2000-01-02  1.490752  bar    1
2000-01-03 -1.387326  foo    2
2000-01-04  0.814772  baz     
2000-01-05 -0.222552         4
2000-01-06 -1.176781  qux     

Zaangażowany w to:

                   A     B     C
2000-01-01 -0.532681   foo     0
2000-01-02  1.490752   bar     1
2000-01-03 -1.387326   foo     2
2000-01-04  0.814772   baz   NaN
2000-01-05 -0.222552   NaN     4
2000-01-06 -1.176781   qux   NaN

Udało mi się to zrobić z poniższym kodem, ale stary czy to brzydkie. To nie jest Pythonic i jestem pewien, że nie jest to również najbardziej efektywne wykorzystanie pand. Przechodzę w pętli przez każdą kolumnę i zastępuję wartość logiczną względem maski kolumny wygenerowanej przez zastosowanie funkcji, która wyszukuje wyrażenia regularne każdej wartości, dopasowując je do białych znaków.

for i in df.columns:
    df[i][df[i].apply(lambda i: True if re.search('^\s*$', str(i)) else False)]=None

Można go nieco zoptymalizować, przechodząc tylko przez pola, które mogą zawierać puste ciągi:

if df[i].dtype == np.dtype('object')

Ale to nie jest duża poprawa

I wreszcie, ten kod ustawia ciągi docelowe na None, co działa z takimi funkcjami Pandy, jak fillna(), ale byłoby miło dla kompletności, gdybym mógł wstawić NaNbezpośrednio zamiast None.

Chris Clark
źródło
2
To, czego naprawdę chcesz, to móc używać replacez wyrażeniem regularnym ... (być może powinno to być wymagane jako funkcja).
Andy Hayden
3
Zrobiłem problem na githubie dla tej funkcji: github.com/pydata/pandas/issues/2285 . Byłbym wdzięczny za PR! :)
Chang She
Dla tych, którzy chcą pominąć jeden pusty znak, zobacz to proste rozwiązanie poniżej
Ted Petrou,

Odpowiedzi:

198

Myślę, że df.replace()spełnia swoje zadanie, ponieważ pandy 0.13 :

df = pd.DataFrame([
    [-0.532681, 'foo', 0],
    [1.490752, 'bar', 1],
    [-1.387326, 'foo', 2],
    [0.814772, 'baz', ' '],     
    [-0.222552, '   ', 4],
    [-1.176781,  'qux', '  '],         
], columns='A B C'.split(), index=pd.date_range('2000-01-01','2000-01-06'))

# replace field that's entirely space (or empty) with NaN
print(df.replace(r'^\s*$', np.nan, regex=True))

Produkuje:

                   A    B   C
2000-01-01 -0.532681  foo   0
2000-01-02  1.490752  bar   1
2000-01-03 -1.387326  foo   2
2000-01-04  0.814772  baz NaN
2000-01-05 -0.222552  NaN   4
2000-01-06 -1.176781  qux NaN

Jak wskazał Temak , użyj, df.replace(r'^\s+$', np.nan, regex=True)jeśli twoje prawidłowe dane zawierają spacje.

patricksurry
źródło
1
regex jest flagą logiczną. Może masz na myśli to, pd.Series(["1", "#", "9", " .", None]).replace(r"( +\.)|#", "X", regex=True).valuesco daje['1', 'X', '9', 'X', None]
patricksurry
2
2 lata później zmieniłem zaakceptowaną odpowiedź na to pytanie, teraz, gdy pandy to obsługują. Dzięki!
Chris Clark
35
UWAGA : jeśli nie chcesz, aby element zawierający spację w środku został zastąpiony przez NaN, użyjdf.replace(r'^\s+$', np.nan, regex=True)
Temak
7
Próbowałem tego użyć, ale okazało się, że r '^ \ s * $' powinno być wyrażeniem, którego należy użyć. bez ^ i $ dopasuje dowolny ciąg z dwoma kolejnymi odstępami. Zmieniono także + na *, aby uwzględnić pusty ciąg „” na liście rzeczy do konwersji na NaN
Master Yogurt
1
Próbuję twojego rozwiązania w moim kodzie, ale nie ma to żadnego skutku. Próbuję "energia [" Zasilanie "]. Replace (to_replace =" ... ", value = np.NaN)". Chce zmienić ciąg „...” na wartości NaN, ale nic nie robi i zwraca tę samą ramkę danych.
Archan Joshi,
50

Jeśli chcesz zastąpić pusty ciąg i rekordy tylko spacjami, prawidłowa odpowiedź to !:

df = df.replace(r'^\s*$', np.nan, regex=True)

Zaakceptowana odpowiedź

df.replace(r'\s+', np.nan, regex=True)

Nie zastępuje pustego ciągu !, możesz spróbować z podanym przykładem nieco zaktualizowanym:

df = pd.DataFrame([
    [-0.532681, 'foo', 0],
    [1.490752, 'bar', 1],
    [-1.387326, 'fo o', 2],
    [0.814772, 'baz', ' '],     
    [-0.222552, '   ', 4],
    [-1.176781,  'qux', ''],         
], columns='A B C'.split(), index=pd.date_range('2000-01-01','2000-01-06'))

Zwróć też uwagę, że „fo o” nie jest zastępowane przez Nan, chociaż zawiera spację. Dalej zauważ, że proste:

df.replace(r'', np.NaN)

To też nie działa - wypróbuj.

Philipp Schwarz
źródło
33

Co powiesz na:

d = d.applymap(lambda x: np.nan if isinstance(x, basestring) and x.isspace() else x)

applymapFunkcja ma zastosowanie funkcji do każdej komórce dataframe.

BrenBarn
źródło
Co za niezła poprawa! Powinienem był o tym pomyśleć z perspektywy czasu, ale z jakiegoś powodu złapałem się na robieniu logicznych zamienników. Jedno pytanie - czy jest przewaga wykonywania sprawdzania basestring w porównaniu z samym str (x) .isspace ()?
Chris Clark,
1
@ChrisClark: Każdy jest w porządku, chociaż przypuszczam, że isinstancebędzie trochę szybszy.
BrenBarn
13
Odniesienie do „basestring” w powyższym kodzie nie będzie działać w Pythonie 3… w takim przypadku spróbuj użyć „str”.
Spike Williams,
4
Zauważ, że to rozwiązanie nie zastępuje pustych ciągów ''. Aby wziąć pod uwagę również puste łańcuchy, użyj:d = d.applymap(lambda x: np.nan if isinstance(x, basestring) and (not x or x.isspace()) else x)
tuomastik
18

Zrobię to:

df = df.apply(lambda x: x.str.strip()).replace('', np.nan)

lub

df = df.apply(lambda x: x.str.strip() if isinstance(x, str) else x).replace('', np.nan)

Możesz usunąć wszystkie str, a następnie zamienić puste str na np.nan.

Xiaorong Liao
źródło
lambda x: x.str.strip () powinna mieć wartość lambda x: x.strip ()? drobna sugestia: dodaj .astype (str) na początku, to rozwiązuje inne problemy z danymi. To działa dla mnie: df = df.apply ['column']. Astype (str) .apply (lambda x: x.strip ()). Replace ('', np.nan)
Wouter
Drugi wiersz kodu obsługuje kolumny typu int / float i string. Miły. Tks!
Kate Stohr
6

Najprostsze ze wszystkich rozwiązań:

df = df.replace(r'^\s+$', np.nan, regex=True)
Gil Baggio
źródło
5

Jeśli eksportujesz dane z pliku CSV, może to być tak proste:

df = pd.read_csv(file_csv, na_values=' ')

Spowoduje to utworzenie ramki danych oraz zastąpienie pustych wartości jako Na

ibrahim rupawala
źródło
2
Inna opcja… użycie skipinitialspace=Trueusuwa również wszelkie białe znaki po separatorze, które spowodowałyby odczytanie dowolnej długości białych znaków i pustych ciągów jako nan. Jeśli jednak chcesz zachować początkowe spacje z jakiegokolwiek powodu, ta opcja nie jest dobrym wyborem.
Rajshekar Reddy
1
@RajshekarReddy, czy możesz umieścić to gdzieś jako odpowiedź, to było genialne!
User2321
2

Aby uzyskać bardzo szybkie i proste rozwiązanie, w którym sprawdzasz równość z pojedynczą wartością, możesz użyć tej maskmetody.

df.mask(df == ' ')
Ted Petrou
źródło
1

Wszystko to jest bliskie właściwej odpowiedzi, ale nie powiedziałbym, że żaden problem rozwiązuje problem, pozostając najbardziej czytelnym dla innych czytających Twój kod. Powiedziałbym, że ta odpowiedź jest połączeniem odpowiedzi BrenBarna i komentarza tuomasttik pod tą odpowiedzią . Odpowiedź BrenBarn wykorzystujeisspace wbudowane, ale nie obsługuje usuwania pustych ciągów, zgodnie z żądaniem OP, i miałbym tendencję do przypisywania tego jako standardowego przypadku zastępowania ciągów przez null.

Przepisałem to za pomocą .apply, więc możesz to nazwać na pd.Serieslub pd.DataFrame.


Python 3:

Aby zamienić puste ciągi lub ciągi całkowicie spacji:

df = df.apply(lambda x: np.nan if isinstance(x, str) and (x.isspace() or not x) else x)

Aby zamienić ciągi całkowicie spacji:

df = df.apply(lambda x: np.nan if isinstance(x, str) and x.isspace() else x)

W tym celu użyć w Pythonie 2, trzeba wymienić strz basestring.

Python 2:

Aby zamienić puste ciągi lub ciągi całkowicie spacji:

df = df.apply(lambda x: np.nan if isinstance(x, basestring) and (x.isspace() or not x) else x)

Aby zamienić ciągi całkowicie spacji:

df = df.apply(lambda x: np.nan if isinstance(x, basestring) and x.isspace() else x)
spen.smith
źródło
1

To zadziałało dla mnie. Kiedy importuję mój plik csv, dodałem na_values ​​= ''. Spacje nie są uwzględniane w domyślnych wartościach NaN.

df = pd.read_csv (ścieżka pliku, na_values ​​= '')

sambrowne
źródło
0

możesz też użyć do tego filtra.

df = PD.DataFrame([
    [-0.532681, 'foo', 0],
    [1.490752, 'bar', 1],
    [-1.387326, 'foo', 2],
    [0.814772, 'baz', ' '],     
    [-0.222552, '   ', 4],
    [-1.176781,  'qux', '  '])
    df[df=='']='nan'
    df=df.astype(float)
ERIC
źródło
Każda linia tego kodu (bez danych) jest błędna.
Julius
0
print(df.isnull().sum()) # check numbers of null value in each column

modifiedDf=df.fillna("NaN") # Replace empty/null values with "NaN"

# modifiedDf = fd.dropna() # Remove rows with empty values

print(modifiedDf.isnull().sum()) # check numbers of null value in each column
Jayantha
źródło
0

To nie jest eleganckie rozwiązanie, ale wydaje się, że działa, to zapisywanie do XLSX, a następnie importowanie go z powrotem. Inne rozwiązania na tej stronie nie działają dla mnie, nie jestem pewien dlaczego.

data.to_excel(filepath, index=False)
data = pd.read_excel(filepath)
David Kong
źródło