578 Stimmen

Erneuern Sie Werte in der Pandas-Spalte mit einem Dictionary und bewahren Sie NaNs.

Ich habe ein Wörterbuch, das so aussieht: di = {1: "A", 2: "B"}

Ich möchte es auf die col1-Spalte eines ähnlichen Dataframes anwenden:

     col1   col2
0       w      a
1       1      2
2       2    NaN

um folgendes zu erhalten:

     col1   col2
0       w      a
1       A      2
2       B    NaN

Wie kann ich das am besten machen?

4voto

Mykola Zotko Punkte 11977

Sie können Ihr Zuordnungswörterbuch mit fehlenden Paaren aus dem DataFrame aktualisieren. Zum Beispiel:

df = pd.DataFrame({'col1': ['a', 'b', 'c', 'd', np.nan]})
map_ = {'a': 'A', 'b': 'B', 'd': np.nan}

# Mapping aus df abrufen
uniques = df['col1'].unique()
map_new = dict(zip(uniques, uniques))
# {'a': 'a', 'b': 'b', 'c': 'c', 'd': 'd', nan: nan}

# Zuordnung aktualisieren
map_new.update(map_)
# {'a': 'A', 'b': 'B', 'c': 'c', 'd': nan, nan: nan}

df['col2'] = df['col1'].map(dct_map_new)

Ergebnis:

  col1 col2
0    a    A
1    b    B
2    c    c
3    d  NaN
4  NaN  NaN

3voto

Nico Coallier Punkte 676

Zusätzlich zu dieser Frage, wenn Sie mehr als eine Spalte in einem Daten-Dataframe neu zuordnen müssen:

def remap(data,dict_labels):
    """
    Diese Funktion nimmt ein Wörterbuch von Labels: dict_labels
    und ersetzt die Werte (zuvor Labelencode) durch den String.

    z.B.: dict_labels = {{'col1':{1:'A',2:'B'}}

    """
    for field,values in dict_labels.items():
        print("Ich remappe %s"%field)
        data.replace({field:values},inplace=True)
    print("FERTIG")

    return data

Hoffentlich kann es für jemanden nützlich sein.

Prost

3voto

U12-Forward Punkte 64772

Oder verwenden Sie apply:

df['col1'].apply(lambda x: {1: "A", 2: "B"}.get(x,x))

Demonstration:

>>> df['col1']=df['col1'].apply(lambda x: {1: "A", 2: "B"}.get(x,x))
>>> df
  col1 col2
0    w    a
1    1    2
2    2  NaN
>>>

2voto

dorien Punkte 5107

Eine schöne Komplettlösung, die eine Karte Ihrer Klassenaufkleber behält:

labels = features['col1'].unique()
labels_dict = dict(zip(labels, range(len(labels))))
features = features.replace({"col1": labels_dict})

Auf diese Weise können Sie jeden beliebigen Zeitpunkt auf das ursprüngliche Klassenaufkleber von labels_dict verweisen.

2voto

not a robot Punkte 3525

TL;DR: Verwenden Sie map+fillna für große di und verwenden Sie replace für kleine di


1. Eine Alternative: np.select()

Wenn das Umsetzungsverzeichnis nicht zu groß ist, ist eine weitere Option numpy.select. Die Syntax von np.select erfordert separate Arrays/Listen von Bedingungen und Ersatzwerten, sodass die Schlüssel und Werte von di getrennt sein sollten.

import numpy as np
df['col1'] = np.select((df[['col1']].values == list(di)).T, di.values(), df['col1'])

N. B. Wenn das Umsetzungsverzeichnis di sehr groß ist, kann es zu Speicherproblemen führen, da, wie Sie in der obigen Codezeile sehen können, ein boolesches Array der Form (len(df), len(di)) erforderlich ist, um die Bedingungen auszuwerten.

2. map+fillna vs replace. Was ist besser?

Wenn wir uns den Quellcode anschauen, ist map, wenn ein Wörterbuch übergeben wird, eine optimierte Methode, die eine Cython-optimierte take_nd()-Funktion aufruft, um die Ersetzungen vorzunehmen, und fillna() ruft where() (eine weitere optimierte Methode) auf, um Werte zu füllen. Andererseits wird replace() in Python implementiert und verwendet eine Schleife über das Wörterbuch. Wenn das Wörterbuch also groß ist, kann replace potenziell tausendmal langsamer sein als map+fillna. Lassen Sie uns den Unterschied am folgenden Beispiel verdeutlichen, bei dem ein einzelner Wert (0) in der Spalte ersetzt wird (eine unter Verwendung eines Wörterbuchs der Länge 1000 (di1) und eine unter Verwendung eines Wörterbuchs der Länge 1 (di2)).

df = pd.DataFrame({'col1': range(1000)})
di1 = {k: k+1 for k in range(-1000, 1)}
di2 = {0: 1}

%timeit df['col1'].map(di1).fillna(df['col1'])
# 1.19 ms ± 6.77 µs pro Schleife (Mittelwert ± Standardabweichung von 7 Schleifen, 1.000 Schleifen pro Schleife)

%timeit df['col1'].replace(di1)
# 41,4 ms ± 400 µs pro Schleife (Mittelwert ± Standardabweichung von 7 Schleifen, 100 Schleifen pro Schleife)

%timeit df['col1'].map(di2).fillna(df['col1'])
# 691 µs ± 27,9 µs pro Schleife (Mittelwert ± Standardabweichung von 7 Schleifen, 1.000 Schleifen pro Schleife)

%timeit df['col1'].replace(di2)
# 157 µs ± 3,34 µs pro Schleife (Mittelwert ± Standardabweichung von 7 Schleifen, 10.000 Schleifen pro Schleife)

Wie Sie sehen können, ist wenn len(di)==1000, replace 35 Mal langsamer, aber wenn len(di)==1, ist es 4.5 Mal schneller. Dieser Unterschied wird größer, wenn die Größe des Umsetzungsverzeichnisses di zunimmt.

Tatsächlich können wir anhand der Leistungsplots folgende Beobachtungen machen. Die Plots wurden mit bestimmten Parametern in jedem Diagramm erstellt. Sie können den Code unten verwenden, um die Größe des Dataframes zu ändern, um unterschiedliche Parameter zu sehen, aber er wird sehr ähnliche Plots erzeugen.

  • Für einen gegebenen DataFrame macht map+fillna Ersetzungen in fast konstanter Zeit, unabhängig von der Größe des Umsetzungsverzeichnisses, während replace schlechter wird, je größer das Umsetzungsverzeichnis wird (oben links).
  • Der Prozentsatz der ersetzten Werte im DataFrame hat kaum Einfluss auf den Laufzeitunterschied. Der Einfluss der Länge von di übertrifft vollständig jeden Einfluss, den er hat (oben rechts).
  • Für ein gegebenes Umsetzungsverzeichnis führt map+fillna besser als replace aus, wenn die Größe des Dataframes zunimmt (unten links).
  • Wenn di groß ist, ist die Größe des Dataframes unerheblich; map+fillna ist viel schneller als replace (unten rechts).

perfplot

Verwendeter Code zur Erstellung der Plots:

import numpy as np
import pandas as pd
from perfplot import plot
import matplotlib.pyplot as plt

kernels = [lambda df,di: df['col1'].replace(di), 
           lambda df,di: df['col1'].map(di).fillna(df['col1'])]
labels = ["replace", "map+fillna"]

# Erstes Diagramm
N, m = 100000, 20
plot(
    setup=lambda n: (pd.DataFrame({'col1': np.resize(np.arange(m*n), N)}), 
                     {k: (k+1)/2 for k in range(n)}),
    kernels=kernels, labels=labels,
    n_range=range(1, 21),
    xlabel='Länge des Ersatzverzeichnisses',
    title=f'Werte in einer Spalte umsetzen (len(df)={N:,}, {100//m}% ersetzt)',
    equality_check=pd.Series.equals)
_, xmax = plt.xlim()
plt.xlim((0.5, xmax+1))
plt.xticks(np.arange(1, xmax+1, 2));

# Zweites Diagramm
N, m = 100000, 1000
di = {k: (k+1)/2 for k in range(m)}
plot(
    setup=lambda n: pd.DataFrame({'col1': np.resize(np.arange((n-100)*m//100, n*m//100), N)}),
    kernels=kernels, labels=labels,
    n_range=[1, 5, 10, 15, 25, 40, 55, 75, 100],
    xlabel='Prozentsatz der ersetzten Werte',
    title=f'Werte in einer Spalte umsetzen (len(df)={N:,}, len(di)={m})',
    equality_check=pd.Series.equals);

# Drittes Diagramm
m, n = 10, 0.01
di = {k: (k+1)/2 for k in range(m)}
plot(
    setup=lambda N: pd.DataFrame({'col1': np.resize(np.arange((n-1)*m, n*m), N)}),
    kernels=kernels, labels=labels,
    n_range=[2**k for k in range(6, 21)], 
    xlabel='Länge des Dataframes',
    logy=False,
    title=f'Werte in einer Spalte umsetzen (len(di)={m}, {int(n*100)}% ersetzt)',
    equality_check=pd.Series.equals);

# Viertes Diagramm
m, n = 100, 0.01
di = {k: (k+1)/2 for k in range(m)}
plot(
    setup=lambda N: pd.DataFrame({'col1': np.resize(np.arange((n-1)*m, n*m), N)}),
    kernels=kernels, labels=labels,
    n_range=[2**k for k in range(6, 21)], 
    xlabel='Länge des Dataframes',
    title=f'Werte in einer Spalte umsetzen (len(di)={m}, {int(n*100)}% ersetzt)',
    equality_check=pd.Series.equals);

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X