5 unverzichtbare Python-Konzepte für Datenwissenschaftler

# Einführung

Sie sollten Python nicht für die Datenwissenschaft verwenden, nur „weil es alle anderen tun!“ Die Dominanz von Python im Datenbereich ist kein Zufall. Es handelt sich um eine Sprache, die auf einer äußerst ausdrucksstarken, lesbaren Syntax basiert und die Speicherverwaltung auf niedriger Ebene abstrahiert. Allerdings ist dieselbe Abstraktion auf hoher Ebene mit Kosten verbunden: Die standardmäßige Python-Ausführung wird dynamisch typisiert und interpretiert, was die reine Iteration schmerzhaft verlangsamen kann.

Um leistungsstarke Datensysteme zu schreiben, muss ein Datenwissenschaftler von standardmäßigen prozeduralen Codierungsmustern zu spezialisierten, vektorisierten und speicherbewussten Ansätzen übergehen. In diesem Artikel werden wir uns eingehend mit fünf unverzichtbaren Python-Konzepten befassen, die Ihnen beim Übergang vom Schreiben von klobigem, langsamem Spaghetti-Code zum Aufbau blitzschneller, produktionstauglicher und wunderbar funktionaler Datenpipelines helfen.

# 1. NumPy-Vektorisierung

Commonplace-Python-Schleifen sind langsam. Da Python eine interpretierte Sprache ist, ist jede Iteration von a for Die Schleife verursacht einen erheblichen Mehraufwand: Typprüfung, dynamische Methodensuche und Referenzzählung. Wenn Sie Millionen von Datenpunkten verarbeiten, summieren sich diese Mikro-Overhead-Kosten zu Engpässen von mehreren Sekunden.

Die Lösung ist NumPy Vektorisierung. Anstatt Elemente sequentiell im Python-Bytecode zu verarbeiten, verlagert NumPy Schleifen auf hochoptimierte, vorkompilierte C-Erweiterungen. Diese Operationen wirken sich auf ganze Arrays auf einmal aus und führen aufeinanderfolgende Array-Blöcke auf Maschinenebene aus, wobei häufig SIMD-Anweisungen (Single Instruction, A number of Information) zum Einsatz kommen.

// Der klobige Weg

Angenommen, wir haben eine Liste mit einer Million Float-Werten, die rohe Sensormesswerte darstellen, und wir müssen jeden Messwert um 1,5 skalieren und eine Kalibrierungskonstante von 10,0 anwenden. Verwendung einer iterativen Python-Schleife:

import time

# A big record of 10 million sensor readings
n_elements = 10_000_000
data_list = (float(x) for x in vary(n_elements))

# Scaling values utilizing an express python loop
start_time = time.time()
scaled_list = ()

for val in data_list:
    scaled_list.append(val * 1.5 + 10.0)

loop_duration = time.time() - start_time

print(f"Loop implementation took: {loop_duration:.6f} seconds")

Ausgabe:

Loop implementation took: 0.378866 seconds

// Der vektorisierte Weg

Hier ist die elegante, vektorisierte Various. Wir laden die Daten in ein zusammenhängendes NumPy-Array und führen die Arithmetik direkt am Array-Objekt durch:

import numpy as np
import time

# A big record of 10 million sensor readings
n_elements = 10_000_000

# Vectorized means: NumPy performs the complete calculation in pre-compiled C loops
data_array = np.arange(n_elements, dtype=float)

start_time = time.time()
scaled_array = data_array * 1.5 + 10.0
numpy_duration = time.time() - start_time

print(f"NumPy implementation took: {numpy_duration:.6f} seconds")
print(f"Speedup: {loop_duration / numpy_duration:.1f}x sooner!")

Ausgabe:

Loop implementation took: 0.348456 seconds
NumPy implementation took: 0.013395 seconds
Speedup: 26.0x sooner!

Durch die Vektorisierung der Arithmetik können wir mit saubererem, prägnanterem Code eine enorme Leistungssteigerung erzielen. Die Schleife wird aus dem Python-Bereich entfernt und vollständig im Hochgeschwindigkeits-C-Bereich ausgeführt.

# 2. Rundfunk: Mathematische Regeln für nicht übereinstimmende Dimensionen

In der linearen Algebra erfordern Matrixoperationen im Allgemeinen, dass beide Operanden genau die gleiche Kind haben. In der Datenwissenschaft müssen wir jedoch häufig Operationen an Arrays unterschiedlicher Dimensionen durchführen, z. B. das Subtrahieren von Function-Spaltendurchschnitten von einem Datensatz oder das Normalisieren von Zeilenwerten.

Anstatt Daten zu duplizieren, um übereinstimmende Formen zu erzwingen, verwendet NumPy eine Reihe mathematischer Regeln namens Rundfunk. Broadcasting ermöglicht elementweise Operationen an Arrays unterschiedlicher Kind, indem das kleinere Array entlang der fehlenden oder Einzelelementdimensionen virtuell erweitert wird, ohne dass Daten in den Speicher kopiert werden.

Die Senderegeln lauten:

  1. Wenn die Arrays nicht den gleichen Rang (Anzahl der Dimensionen) haben, stellen Sie der Kind des Arrays mit dem niedrigeren Rang Einsen voran, bis beide Formen die gleiche Länge haben
  2. Zwei Dimensionen sind kompatibel, wenn sie gleich sind oder wenn eine von ihnen 1 ist
  3. Bei Kompatibilität verhält sich das Array so, als ob es entlang der Dimension der Größe 1 gestreckt würde, um der Kind des anderen Arrays zu entsprechen

// Der klobige Weg

Angenommen, wir haben eine 3×4-Merkmalsmatrix (3 Stichproben, 4 Merkmale) und möchten die Spaltenmittelwerte subtrahieren, um die Merkmale zu „entmitteln“:

import numpy as np

options = np.array((
    (10.0, 20.0, 30.0, 4.0),
    (12.0, 24.0, 36.0, 8.0),
    (14.0, 28.0, 42.0, 12.0)
))

# Imply of every characteristic column (form: (4,))
col_means = np.imply(options, axis=0)

# Utilizing nested loops to manually de-mean
demeaned_clunky = np.zeros_like(options)
for idx in vary(options.form(0)):
    for col_idx in vary(options.form(1)):
        demeaned_clunky(idx, col_idx) = options(idx, col_idx) - col_means(col_idx)

# Various: tiling the array to drive matching shapes
tiled_means = np.tile(col_means, (options.form(0), 1))
demeaned_tiled = options - tiled_means

// Der pythonische Weg

Beim Broadcasting führen wir die Subtraktion direkt durch. NumPy richtet die automatisch aus (3, 4) Merkmalsmatrix mit der (4,) Spaltenmittelwert-Array durch Behandeln der Spaltenmittelwertform als (1, 4):

import numpy as np

options = np.array((
    (10.0, 20.0, 30.0, 4.0),
    (12.0, 24.0, 36.0, 8.0),
    (14.0, 28.0, 42.0, 12.0)
))

col_means = np.imply(options, axis=0)

# Pythonic subtraction by way of automated broadcasting
demeaned_broadcasting = options - col_means

# Dividing every row by its row sum
# row_sums has form (3,) -> to divide (3, 4) by (3,), we broaden form to (3, 1) utilizing np.newaxis
row_sums = np.sum(options, axis=1)
normalized_features = options / row_sums(:, np.newaxis)

print("Demeaned:n", demeaned_broadcasting)
print("nNormalized Rows:n", normalized_features)

Ausgabe:

Demeaned:
 ((-2. -4. -6. -4.)
 ( 0.  0.  0.  0.)
 ( 2.  4.  6.  4.))

Normalized Rows:
 ((0.15625    0.3125     0.46875    0.0625    )
 (0.15       0.3        0.45       0.1       )
 (0.14583333 0.29166667 0.4375     0.125     ))

Durch Broadcasting werden doppelte Werte und das Kopieren aus dem Speicher vermieden. Unter der Haube führt NumPy die Subtraktionsschleifen mit C-Geschwindigkeit aus, ohne eine gekachelte Zwischenmatrix zu erstellen, wodurch die Speicherbandbreite erhalten bleibt und Vorgänge beschleunigt werden.

# 3. Die Pandas-Methoden .pipe() und .assign(): Saubere, funktionale Pipelines

Datenaufbereitung in Pandas degeneriert oft zu sequentiellem Spaghetti-Code. Entwickler erstellen mehrere Zwischen-DataFrames (df1, df2usw.), Variablen direkt ändern oder Klammern verketten. Dies führt zu Code, der schwer zu lesen, schwer zu testen und bekanntermaßen anfällig für gefürchtete Fehler ist SettingWithCopyWarning.

Trendy Pandas fördert die Abkehr von prozeduralen Mutationen hin zu funktionalen, deklarativen Datenpipelines. Durch die Nutzung .assign() zur Function-Erstellung und .pipe() Für wiederverwendbare mehrspaltige Vorgänge können Sie Schritte in einer einzigen Pipeline verketten.

// Der klobige Weg

Nehmen wir einen Rohdatensatz zu Kundenverkäufen, der das Filtern von Ausreißern, das Standardisieren von Zeichenfolgen, das Zurechnen von Werten und das Berechnen von Umsatzsteuern erfordert.

import pandas as pd
import numpy as np

raw_data = {
    'Customer_ID': (101, 102, 103, 104, 105),
    'Age': (25, -5, 47, 120, 31),
    'Nation': ('usa', 'CANADA', 'usa', 'Germany', 'canada'),
    'Raw_Spend': (120.50, 450.00, 80.00, np.nan, 300.00)
}
df = pd.DataFrame(raw_data)

# Sequential intermediate mutations
df_clean = df.copy()

# 1. Filter out invalid ages
df_clean = df_clean((df_clean('Age') >= 0) & (df_clean('Age') <= 100))

# 2. Standardize nation names (dangers copy warnings)
df_clean('Nation') = df_clean('Nation').str.higher().str.strip()

# 3. Impute lacking Raw_Spend values
median_spend = df_clean('Raw_Spend').median()
df_clean('Raw_Spend') = df_clean('Raw_Spend').fillna(median_spend)

# 4. Calculate Taxed_Spend
df_clean('Taxed_Spend') = df_clean('Raw_Spend') * 1.15

# 5. Format Column Names
df_clean = df_clean.rename(columns={'Customer_ID': 'customer_id'})

// Der pythonische Weg

Indem wir dies als ein Drawback der funktionalen Methodenverkettung betrachten, können wir den Länderstandardisierungsschritt in eine wiederverwendbare Dienstprogrammfunktion einbinden und eine einzelne, saubere, in sich geschlossene Pipeline konstruieren.

import pandas as pd
import numpy as np

raw_data = {
    'Customer_ID': (101, 102, 103, 104, 105),
    'Age': (25, -5, 47, 120, 31),
    'Nation': ('usa', 'CANADA', 'usa', 'Germany', 'canada'),
    'Raw_Spend': (120.50, 450.00, 80.00, np.nan, 300.00)
}
df = pd.DataFrame(raw_data)

# Reusable customized transformation perform for .pipe()
def standardize_countries(dataframe: pd.DataFrame) -> pd.DataFrame:
    df_out = dataframe.copy()
    df_out('Nation') = df_out('Nation').str.higher().str.strip()
    return df_out

# Single elegant purposeful pipeline
df_clean_pipeline = (
    df.question("Age >= 0 and Age <= 100")
      .assign(
          Raw_Spend=lambda x: x('Raw_Spend').fillna(x('Raw_Spend').median()),
          Taxed_Spend=lambda x: x('Raw_Spend') * 1.15
      )
      .pipe(standardize_countries)
      .rename(columns={'Customer_ID': 'customer_id'})
)

print(df_clean_pipeline)

Ausgabe:

   customer_id  Age Nation  Raw_Spend  Taxed_Spend
0          101   25     USA      120.5     138.5750
2          103   47     USA       80.0      92.0000
4          105   31  CANADA      300.0     345.0000

Durch die Methodenverkettung wird sichergestellt, dass der Standing Ihres ursprünglichen DataFrame niemals versehentlich geändert wird, wodurch Nebenwirkungen vermieden werden. .assign() Behandelt Spaltenzuweisungen durch den Empfang einer Lambda-Funktion wo x bezieht sich auf den aktiven Zustand des DataFrame an diesem Punkt in der Kette, während .pipe() ermöglicht die saubere Modularisierung benutzerdefinierter Vorgänge.

# 4. Lambda-Funktionen für Datentransformationen

Für das Function-Engineering sind häufig kleine Transformationen mit einem einzigen Zweck erforderlich, etwa das Formatieren von Zeichenfolgen, das Aufteilen von Werten oder das Anwenden von bedingten Anweisungen. Schreiben benutzerdefinierter benannter Funktionen (mit def) für diese einfachen Berechnungen fügt unnötige Boilerplate zu Ihrem Skript hinzu.

Ein eleganterer Ansatz ist die Verwendung Lambda-Funktionen im Inneren von Pandas .map() Und .apply(). Lambda-Funktionen sind anonyme Wegwerffunktionen, die spontan und ohne Namen definiert werden und sich perfekt für die schnelle Datenzuordnung und saubere Inline-Transformationen eignen.

// Der klobige Weg

Angenommen, wir haben einen Datensatz von Mitarbeitern und müssen ihren Distant-Arbeitsstatus abbilden und ihre Nachnamen analysieren. Ein häufiger Fehler besteht darin, manuelle Schleifen zu schreiben oder zu verwenden iterrows():

import pandas as pd

df = pd.DataFrame({
    'employee_name': ('john doe', 'jane smith', 'bob johnson'),
    'department_code': ('IT_01', 'HR_02', 'IT_03'),
    'is_remote': (1, 0, 1)
})

# Row-by-row iteration (sluggish and verbosely managed)
df_clunky = df.copy()
df_clunky('remote_status') = None
df_clunky('last_name') = None

for index, row in df_clunky.iterrows():
    # Parsing distant standing
    if row('is_remote') == 1:
        df_clunky.at(index, 'remote_status') = "Distant"
    else:
        df_clunky.at(index, 'remote_status') = "Workplace"
    
    # Parsing and capitalizing final identify
    name_parts = row('employee_name').break up()
    df_clunky.at(index, 'last_name') = name_parts(1).capitalize()

// Der pythonische Weg

Hier ist der saubere, deklarative Ansatz mit Inline-Lambda-Transformationen. Wir wenden anonyme Inline-Logik an, um Spalten sofort umzuwandeln .map() für einfache Konvertierungen und .apply() für benutzerdefinierte Zeichenfolgenoperationen:

import pandas as pd

df = pd.DataFrame({
    'employee_name': ('john doe', 'jane smith', 'bob johnson'),
    'department_code': ('IT_01', 'HR_02', 'IT_03'),
    'is_remote': (1, 0, 1)
})

# Lambdas nested inside map() and apply()
df_opt = df.assign(
    remote_status=lambda d: d('is_remote').map(lambda val: "Distant" if val == 1 else "Workplace"),
    last_name=lambda d: d('employee_name').apply(lambda identify: identify.break up()(-1).capitalize()),
    dept_level=lambda d: d('department_code').apply(lambda code: code.break up('_')(-1))
)

print(df_opt(('employee_name', 'last_name', 'remote_status', 'dept_level')))

Ausgabe:

  employee_name last_name remote_status dept_level
0      john doe       Doe        Distant         01
1    jane smith     Smith        Workplace         02
2   bob johnson   Johnson        Distant         03

Mithilfe von Lambdas können Sie eigenständige Transformationen schreiben, die Ihre Logik eng an die Anweisungen zur Spaltenerstellung binden. Durch die Kombination von Lambda mit .map() Und .apply()eliminieren Sie ausführliche verschachtelte Schleifen und sorgen dafür, dass Ihr Code intestine lesbar bleibt.

# 5. Speicherverwaltung mit DataFrames: Dtypes optimieren

Wenn Pandas einen Datensatz importiert (z. B. aus CSV- oder Datenbankdateien), geht es standardmäßig auf Nummer sicher. Ganzzahlen werden als 64-Bit geladen (int64), Dezimalzahlen als 64-Bit (float64) und Textspalten als generisch object Typen. Obwohl dies sicher ist, wird standardmäßig der maximale Speicherbedarf verwendet. Ein Datensatz mit nur wenigen hunderttausend Zeilen kann schnell Gigabyte System-RAM verbrauchen, was zu lokalen Verlangsamungen oder „Nicht genügend Arbeitsspeicher“-Fehlern auf Produktionsservern führt.

Wir können den Speicherbedarf eines DataFrames drastisch reduzieren, indem wir numerische Spalten in kleinere Ganzzahlen/Floats umwandeln und Textspalten mit niedriger Kardinalität in konvertieren Kategorie Datentypen.

Beispielsweise enthält eine Altersspalte Werte im Bereich von 0 bis 100, die problemlos in eine einzelne 8-Bit-Ganzzahl passen (int8das Werte bis zu 127 enthält) anstelle der standardmäßigen 64-Bit- (int64) Datentyp. In ähnlicher Weise ordnen Kategoriewerte Textzeichenfolgen unter der Haube einfachen Ganzzahlcodes zu, was zu enormen Platzeinsparungen führt.

// Der klobige Weg

Lassen Sie uns einen synthetischen Abonnentendatensatz von 100.000 Benutzern erstellen und uns den von den Commonplace-Pandas-Typen verbrauchten Speicher ansehen:

import pandas as pd
import numpy as np

n_rows = 100_000
np.random.seed(42)

df_large = pd.DataFrame({
    'user_id': np.random.randint(1000000, 1000000 + n_rows, measurement=n_rows),
    'age': np.random.randint(18, 90, measurement=n_rows),
    'device_type': np.random.selection(('iOS', 'Android', 'Internet', 'SmartTV'), measurement=n_rows),
    'monthly_revenue': np.random.uniform(5.0, 150.0, measurement=n_rows),
    'active_subscriber': np.random.selection((0, 1), measurement=n_rows)
})

# Inspecting reminiscence utilization
print(df_large.information(memory_usage="deep"))
memory_before = df_large.memory_usage(deep=True).sum() / (1024 ** 2)
print(f"Default Reminiscence Utilization: {memory_before:.2f} MB")

Ausgabe:

<class 'pandas.core.body.DataFrame'>
RangeIndex: 100000 entries, 0 to 99999
Information columns (whole 5 columns):
 #   Column             Non-Null Rely   Dtype  
---  ------             --------------   -----  
 0   user_id            100000 non-null  int64  
 1   age                100000 non-null  int64  
 2   device_type        100000 non-null  object 
 3   monthly_revenue    100000 non-null  float64
 4   active_subscriber  100000 non-null  int64  
dtypes: float64(1), int64(3), object(1)
reminiscence utilization: 8.2 MB
None
Default Reminiscence Utilization: 8.20 MB

// Der pythonische Weg

Wenden wir nun unsere Optimierungen an: Spalten auf ihre minimal erforderlichen numerischen Grenzen umwandeln und Textspalten in konvertieren class:

# Downcasting varieties
df_optimized = df_large.assign(
    user_id=df_large('user_id').astype('int32'),                    # Max 1.1 million matches in int32
    age=df_large('age').astype('int8'),                             # Max age 90 matches in int8
    device_type=df_large('device_type').astype('class'),         # Low cardinality (4 distinctive strings)
    monthly_revenue=df_large('monthly_revenue').astype('float32'),  # Single precision float is loads
    active_subscriber=df_large('active_subscriber').astype('int8')  # Binary flag matches in int8
)

# Inspecting optimized reminiscence utilization
print(df_optimized.information(memory_usage="deep"))
memory_after = df_optimized.memory_usage(deep=True).sum() / (1024 ** 2)

print(f"Optimized Reminiscence Utilization: {memory_after:.2f} MB")
print(f"Reminiscence Footprint Discount: {((memory_before - memory_after) / memory_before) * 100:.1f}%")

Ausgabe:

reminiscence utilization: 1.0 MB
None
Optimized Reminiscence Utilization: 1.05 MB
Reminiscence Footprint Discount: 87.2%

Durch einfaches Anpassen unserer Spalte dtypeshaben wir die Größe des DataFrame um verkleinert quick 90 %! Durch die Verwendung class Bei Zeichenfolgen mit geringer Kardinalität vermeidet Pandas das Duplizieren von Zeichenfolgen über Zeilen hinweg und ordnet stattdessen jede Zeile einem einfachen Integer-Index zu.

# Zusammenfassung

Die Beherrschung dieser fünf grundlegenden Python-Konzepte ist ein wichtiger Schritt auf dem Weg zu einem leitenden Datenwissenschaftler, der effiziente, lesbare und hochoptimierte Datenpipelines entwirft.

Durch die Nutzung von Vektorisierung und Broadcasting in NumPy eliminieren Sie rohe Python-Schleifen und ermöglichen Beschleunigungen auf Hardwareebene. Übergang zu funktionsfähigen Pandas-Pipelines mit .pipe() Und .assign() erhöht die Lesbarkeit und Sicherheit Ihrer Function-Engineering-Workflows. Kombinieren Sie diese mit Inline lambda Funktionen für spontane Transformationen und proaktive Speicherverwaltung durch dtypes ermöglicht Ihnen die nahtlose Skalierung Ihrer Algorithmen von lokalen Prototypen bis hin zu riesigen Produktions-Workloads.

In der Datenwissenschaft geht es sowohl um Software program-Engineering als auch um Mathematik. Behandeln Sie Ihren Code als erstklassiges Produkt, und Ihre Datensätze werden schneller verarbeitet, Ihre Pipelines werden weniger ausfallen und die Erstellung Ihrer Systeme wird eine Freude sein.

Schauen Sie sich unbedingt die vorherigen Artikel dieser Serie an:

Matthew Mayo (@mattmayo13) hat einen Grasp-Abschluss in Informatik und ein Diplom in Information Mining. Als geschäftsführender Herausgeber von KDnuggets & Statistikund Mitherausgeber bei Beherrschung des maschinellen LernensZiel von Matthew ist es, komplexe datenwissenschaftliche Konzepte zugänglich zu machen. Zu seinen beruflichen Interessen zählen die Verarbeitung natürlicher Sprache, Sprachmodelle, Algorithmen für maschinelles Lernen und die Erforschung neuer KI. Seine Mission ist es, das Wissen in der Datenwissenschaftsgemeinschaft zu demokratisieren. Matthew programmiert seit seinem sechsten Lebensjahr.



Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert