Scikit-Learn enthält eine nützliche Klasse zum Umgang mit fehlenden Werten: SimpleImputer. Sie verwenden diese, indem Sie zuerst eine Instanz von SimpleImputer erzeugen und angeben, dass Sie die fehlenden Werte jedes Merkmals mit dessen Median ersetzen möchten:
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy="median")
Da der Median sich nur bei numerischen Merkmalen berechnen lässt, müssen wir eine Kopie der Daten unter Ausschluss des Textmerkmals ocean_proximity erzeugen:
housing_num = housing.drop("ocean_proximity", axis=1)
Anschließend können Sie die imputer-Instanz durch Aufrufen der Methode fit() an die Trainingsdaten anpassen:
imputer.fit(housing_num)
Der imputer hat einfach den Median jedes Merkmals berechnet und das Ergebnis im Attribut statistics_ gespeichert. Lediglich beim Merkmal total_bedrooms gab es fehlende Daten, aber wir können uns nicht sicher sein, dass die neuen Daten im Betrieb nicht ebenfalls lückenhaft sind. Daher ist es sicherer, den imputer auf sämtliche numerischen Merkmale anzuwenden:
>>> imputer.statistics_
array([ -118.51 , 34.26 , 29. , 2119.5 , 433. , 1164. , 408. , 3.5409])
>>> housing_num.median().values
array([ -118.51 , 34.26 , 29. , 2119.5 , 433. , 1164. , 408. , 3.5409])
Sie können nun mit diesem »trainierten« imputer den Trainingsdatensatz transformieren, sodass die fehlenden Werte durch die gefundenen Mediane ersetzt werden:
X = imputer.transform(housing_num)
Das Ergebnis ist ein NumPy-Array mit den transformierten Merkmalen. Dieses wieder in ein pandas-DataFrame zu überführen, ist ebenfalls einfach:
housing_tr = pd.DataFrame(X, columns=housing_num.columns,
index=housing_num.index)
Das Design von Scikit-Learn
Das Design der API von Scikit-Learn ist bemerkenswert gut gelungen. Die wichtigsten Designprinzipien (https://homl.info/11) sind:17
Konsistenz. Alle Objekte besitzen eine konsistente, einfache Schnittstelle:– Estimatoren. Jedes Objekt, das Parameter anhand eines Datensatzes abschätzen kann, wird Estimator genannt (z.B. ist ein Imputer ein Estimator). Das Abschätzen der Parameter wird von der Methode fit() durchgeführt, die als Parameter lediglich einen Datensatz benötigt (zwei bei überwachten Lernalgorithmen, der zweite Datensatz enthält in diesem Fall die Labels). Jeder andere für das Abschätzen benötigte Parameter wird als Hyperparameter angesehen (wie strategy beim Imputer) und muss als Attribut der Instanz gesetzt werden (für gewöhnlich als Parameter im Konstruktor).–Transformer. Einige Estimatoren (wie der Imputer) können einen Datensatz außerdem transformieren; diese werden Transformatoren genannt. Wieder einmal ist der Aufbau der API recht einfach: Die Methode trans form() nimmt die Transformation selbst vor, der Datensatz wird als Parameter übergeben. Zurückgegeben wird der transformierte Datensatz. Diese Transformation beruht im Allgemeinen auf den erlernten Parametern, wie es auch beim Imputer der Fall ist. Sämtliche Transformer besitzen außerdem die bequemere Methode fit_transform(), die den aufeinanderfolgenden Aufrufen von fit() und transform() entspricht (manchmal ist fit_transform() aber auf höhere Geschwindigkeit optimiert).–Prädiktoren. Schließlich sind einige Estimatoren in der Lage, auf einem gegebenen Datensatz Vorhersagen zu treffen; diese werden als Prädiktoren bezeichnet. Beispielsweise ist das Modell LinearRegression aus dem vorigen Kapitel ein Prädiktor: Es sagt die Zufriedenheit aus dem Pro-Kopf-BIP eines Lands vorher. Jeder Prädiktor besitzt die Methode pre dict(), die einen Datensatz mit neuen Datenpunkten annimmt und einen Satz entsprechender Vorhersagen zurückgibt. Jeder Prädiktor besitzt darüber hinaus die Methode score() zum Bestimmen der Vorhersagequalität anhand eines Testdatensatzes (und bei überwachten Lernalgorithmen auch der entsprechenden Labels).18
Inspektion. Sämtliche Hyperparameter eines Estimators sind direkt als öffentliche Attribute der Instanz abrufbar (z.B. imputer.strategy), auch die von einem Estimator erlernten Parameter sind über ein öffentliches Attribut mit einem Unterstrich als Suffix verfügbar (z.B. imputer.statistics_).
Nicht-proliferierende Klassen. Datensätze werden in NumPy-Arrays oder Sparse Matrices aus SciPy anstatt in eigenen Klassen abgelegt. Die Hyperparameter sind gewöhnliche Python-Strings oder Zahlen.
Komposition. Existierende Komponenten werden so weit wie möglich wiederverwendet. Beispielsweise können Sie eine Pipeline als beliebige Abfolge von Transformatoren und einem Estimator am Ende definieren und als eigenen Estimator speichern, wie wir noch sehen werden.
Sinnvolle Standardwerte. Die Standardwerte der meisten Parameter in Scikit-Learn sind sinnvoll ausgewählt, sodass Sie schnell ein lauffähiges Grundsystem erstellen können.
Bearbeiten von Text und kategorischen Merkmalen
Bisher haben wir uns nur mit numerischen Attributen befasst, jetzt wollen wir uns um Textattribute kümmern. In diesem Datensatz gibt es nur eines: ocean_proximity. Schauen wir uns die Werte der ersten zehn Instanzen an:
>>> housing_cat = housing[["ocean_proximity"]]
>>> housing_cat.head(10)
ocean_proximity
17606 <1H OCEAN
18632 <1H OCEAN
14650 NEAR OCEAN
3230 INLAND
3555 <1H OCEAN
19480 INLAND
8879 <1H OCEAN
13685 INLAND
4937 <1H OCEAN
4861 <1H OCEAN
Das ist kein freier Text – es gibt eine begrenzte Zahl möglicher Werte, von denen jeder eine Kategorie repräsentiert. Daher handelt es sich bei diesem Attribut um ein kategorisches Merkmal. Die meisten Machine-Learning-Algorithmen bevorzugen Zahlen, daher werden wir diese Kategorien von Text zu Zahlen konvertieren. Dazu verwenden wir die Klasse OrdinalEncoder:19
>>> from sklearn.preprocessing import OrdinalEncoder
>>> ordinal_encoder = OrdinalEncoder()
>>> housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)
>>> housing_cat_encoded[:10]
array([[0.],
[0.],
[4.],
[1.],
[0.],
[1.],
[0.],
[1.],
[0.],
[0.]])
Die Liste der Merkmale erhalten Sie über die Instanzvariable categories_. Dabei handelt es sich um ein eindimensionales Array mit Kategorien für jedes kategorische Merkmal (in diesem Fall enthält die Liste lediglich ein einzelnes Array, da es nur ein kategorisches Merkmal gibt):
>>> ordinal_encoder.categories_
[array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'],
dtype=object)]
Schwierig bei dieser Art der Darstellung ist, dass manche ML-Algorithmen davon ausgehen, zwei benachbarte Werte seien ähnlicher zueinander als zwei weiter