Daten aus der realen Welt sind chaotisch. Jeder, der mit gecrackten Webdaten gearbeitet hat, hat dieses Muster gesehen: als Textual content gespeicherte Preise, unmögliche Jahreswerte, Spalten ohne Variation und numerische Felder, die sich auf verdächtig begrenzte Weise wiederholen. Bei der Bereinigung dieser Daten findet der Großteil der eigentlichen analytischen Arbeit tatsächlich statt.
In diesem Projekt werden wir einen realistischen Datenbereinigungs- und Analyse-Workflow mit a durcharbeiten Datensatz mit 50.000 Gebrauchtwagenangeboten entnommen aus eBay Kleinanzeigen, dem Kleinanzeigenbereich der deutschen eBay-Web site. Wir schlüpfen in die Rolle eines Datenanalysten, der einem Gebrauchtwagen-Kleinanzeigendienst dabei hilft, zu verstehen, wie sich Marke und Kilometerstand auf die Autopreise auswirken.
Was Sie lernen werden
Am Ende dieses Tutorials wissen Sie, wie Sie:
- Laden Sie Daten mit Codierungsproblemen und verstehen Sie, warum diese auftreten
- Bereinigen Sie die Spaltennamen von „camelCase“ bis „snake_case“.
- Identifizieren und löschen Sie nutzlose oder redundante Spalten
- Entfernen Sie Formatierungszeichen aus numerischen Zeichenfolgen und konvertieren Sie Datentypen
- Erkennen und entfernen Sie Preisausreißer mithilfe ermessensbasierter Schwellenwerte
- Filtern Sie ungültige Registrierungsjahre
- Gruppieren und aggregieren Sie Daten nach Marken, um Preistrends aufzudecken
Bevor Sie beginnen
Sie benötigen Python 3.8+, Jupyter Pocket book, Pandas und NumPy. Sie können sie mit installieren pip set up pandas numpy wenn nötig.
Vertrautheit mit Python-Pay attention, Wörterbüchern, Schleifen und grundlegenden Pandas-Operationen wird Ihnen dabei helfen, weiterzumachen. Wenn Sie zuerst eine Rezension lesen möchten, die Einführung in Pandas und NumPy für die Datenanalyse Der Kurs behandelt die Kernkonzepte. Greifen Sie auf das vollständige Projekt zu Dataquest-App und das Lösungsnotizbuch an GitHub.
Der Datensatz
Die Daten wurden ursprünglich von eBay Kleinanzeigen entnommen und auf Kaggle hochgeladen. Wir arbeiten mit einem von Dataquest erstellten Beispiel mit 50.000 Zeilen, das eine weniger bereinigte Model des Originals simuliert. Jede Zeile stellt einen einzelnen Gebrauchtwageneintrag dar, mit Spalten, die den Namen, den Preis, die Marke, das Modell, den Kilometerstand, das Zulassungsjahr, den Kraftstofftyp des Wagens und verschiedene Metadaten zum Eintrag selbst abdecken.
Unser Ziel ist es, diese Daten zu bereinigen und Preistrends nach Marken zu analysieren.
Schritt 1: Laden der Daten
import pandas as pd
import numpy as np
autos = pd.read_csv('autos.csv', encoding='Latin-1')
autos.information()
autos.head()
Wenn Sie versuchen, die Datei zu laden, ohne eine Codierung anzugeben, erhalten Sie eine UnicodeDecodeError. Dies liegt daran, dass der Datensatz nicht-englische Zeichen (deutscher Textual content und Sonderzeichen) enthält, mit denen die standardmäßige UTF-8-Kodierung nicht umgehen kann. Wenn Sie diesen Fehler sehen, encoding='Latin-1' ist oft die Lösung. Latin-1 deckt ein breiteres Spektrum westeuropäischer Schriftzeichen ab und löst das Downside in etwa 90 % der Fälle.
<class 'pandas.core.body.DataFrame'>
RangeIndex: 50000 entries, 0 to 49999
Knowledge columns (complete 20 columns):
# Column Non-Null Depend Dtype
--- ------ -------------- -----
0 dateCrawled 50000 non-null object
1 title 50000 non-null object
...
4 value 50000 non-null object
...
11 odometer 50000 non-null object
...
Ein paar Dinge fallen auf information(). Die meisten Spalten sind object Typ (Strings in Pandas). Vor allem, value Und odometer sind beide Objekte, obwohl sie numerisch sein sollten – ein Blick auf die tatsächlichen Datenwerte zeigt sofort, warum.
value: $5,000 odometer: 150,000km
Beide Spalten enthalten nicht numerische Zeichen: Dollarzeichen und Kommas beim Preis und „km“ plus Kommas beim Kilometerzähler. Diese müssen entfernt werden, bevor wir mit diesen Spalten rechnen können.
Lerneinblick: Der
information()Undhead()Die Kombination dieser Methoden ist Ihre erste Verteidigungslinie in jedem neuen Datensatz.information()teilt Ihnen Datentypen und Nullzahlen mit.head()Zeigt Ihnen die tatsächlichen Werte an, damit Sie sie sehen können Warum Diese Typen sind, was sie sind. Zusammen decken sie viele der Reinigungsaufgaben ab, die Sie erledigen müssen.
Schritt 2: Spalten umbenennen
Die ursprünglichen Spaltennamen verwenden camelCase (dateCrawled, vehicleType, yearOfRegistration). Die Python-Konvention ist „snake_case“, additionally benennen wir sie alle auf einmal um.
autos.columns = ('date_crawled', 'title', 'vendor', 'offer_type', 'value', 'ab_test',
'vehicle_type', 'registration_year', 'gearbox', 'power_ps', 'mannequin',
'odometer', 'registration_month', 'fuel_type', 'model',
'unrepaired_damage', 'ad_created', 'num_photos', 'postal_code',
'last_seen')
Dies erleichtert die Arbeit mit den Spalten und stimmt mit den Greatest Practices von Pandas überein. Beachten Sie, dass wir auch die Gelegenheit genutzt haben, einige Spalten aussagekräftiger umzubenennen: yearOfRegistration wird registration_year, notRepairedDamage wird unrepaired_damageUnd nrOfPictures wird num_photos.
Schritt 3: Unbrauchbare Spalten löschen
Der describe(embrace='all') Die Methode liefert uns zusammenfassende Statistiken für jede Spalte, einschließlich Zeichenfolgenspalten.
autos.describe(embrace='all')
| date_crawled | Title | Verkäufer | Angebotstyp | Preis | ab_test | Fahrzeugtyp | Registrierungsjahr | Getriebe | power_ps | Modell | Kilometerzähler | Registrierungsmonat | Kraftstofftyp | Marke | Nicht reparierter Schaden | ad_created | Anzahl_Fotos | Postleitzahl | zuletzt_gesehen | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| zählen | 50000 | 50000 | 50000 | 50000 | 50000 | 50000 | 44905 | 50000,000000 | 47320 | 50000,000000 | 47242 | 50000 | 50000,000000 | 45518 | 50000 | 40171 | 50000 | 50000,0 | 50000,000000 | 50000 |
| einzigartig | 48213 | 38754 | 2 | 2 | 2357 | 2 | 8 | NaN | 2 | NaN | 245 | 13 | NaN | 7 | 40 | 2 | 76 | NaN | NaN | 39481 |
| Spitze | 10.03.2016 15:36:24 | Ford_Fiesta | privat | Angebot | $0 | prüfen | Limousine | NaN | manuell | NaN | Golf | 150.000 km | NaN | Benzin | Volkswagen | nein | 03.04.2016 00:00:00 | NaN | NaN | 07.04.2016 06:17:27 |
| Frequenz | 3 | 78 | 49999 | 49999 | 1421 | 25756 | 12859 | NaN | 36993 | NaN | 4024 | 32424 | NaN | 30107 | 10687 | 35232 | 1946 | NaN | NaN | 8 |
| bedeuten | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 2005.073280 | NaN | 116.355920 | NaN | NaN | 5.723360 | NaN | NaN | NaN | NaN | 0,0 | 50813.627300 | NaN |
| std | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 105.712813 | NaN | 209.216627 | NaN | NaN | 3.711984 | NaN | NaN | NaN | NaN | 0,0 | 25779.747957 | NaN |
| min | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 1000.000000 | NaN | 0,000000 | NaN | NaN | 0,000000 | NaN | NaN | NaN | NaN | 0,0 | 1067.000000 | NaN |
| 25 % | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 1999.000000 | NaN | 70.000000 | NaN | NaN | 3.000000 | NaN | NaN | NaN | NaN | 0,0 | 30451.000000 | NaN |
| 50 % | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 2003.000000 | NaN | 105.000000 | NaN | NaN | 6.000000 | NaN | NaN | NaN | NaN | 0,0 | 49577.000000 | NaN |
| 75 % | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 2008.000000 | NaN | 150.000000 | NaN | NaN | 9.000000 | NaN | NaN | NaN | NaN | 0,0 | 71540.000000 | NaN |
| max | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 9999.000000 | NaN | 17700.000000 | NaN | NaN | 12.000000 | NaN | NaN | NaN | NaN | 0,0 | 99998.000000 | NaN |
Ein paar Dinge fallen sofort auf. Der vendor Die Spalte enthält nur zwei eindeutige Werte und 49.999 von 50.000 sind identisch. offer_type ist für quick jede Zeile identisch. Diese Spalten enthalten im Wesentlichen keine Informationen.
Der num_photos Kolumne ist noch eindeutiger:
autos("num_photos").value_counts()
0 50000
Title: num_photos, dtype: int64
Jede einzelne Zeile ist Null. Die Spalte ist nutzlos. Wir lassen alle drei fallen.
autos = autos.drop(("num_photos", "vendor", "offer_type"), axis=1)
Schritt 4: Reinigen der Preis- und Kilometerzählerspalten
Nun zur Kernreinigungsarbeit. Beide value Und odometer werden als Zeichenfolgen mit Formatierungszeichen gespeichert, die eine numerische Konvertierung verhindern.
autos("value") = (autos("value")
.str.substitute("$", "")
.str.substitute(",", "")
.astype(int)
)
autos("value").head()
0 5000
1 8500
2 8990
3 4350
4 1350
Title: value, dtype: int64
Dabei handelt es sich um Methodenverkettung: drei Operationen werden nacheinander in einem einzigen lesbaren Block angewendet. Wir entfernen das Dollarzeichen, entfernen das Komma (wird in der deutschen Notation als Tausendertrennzeichen verwendet, wobei Kommas auch Dezimalstellen bezeichnen können) und konvertieren in eine Ganzzahl. Der gleiche Ansatz funktioniert für den Kilometerzähler.
autos("odometer") = (autos("odometer")
.str.substitute("km", "")
.str.substitute(",", "")
.astype(int)
)
autos.rename({"odometer": "odometer_km"}, axis=1, inplace=True)
Wir benennen die Spalte auch in um odometer_km Es ist additionally klar, dass die Einheit Kilometer ist, da wir „km“ aus den Werten selbst entfernen.
Schritt 5: Preis erkunden und bereinigen
Lassen Sie uns vor dem Filtern verstehen, womit wir arbeiten.
print(autos("value").distinctive().form)
print(autos("value").describe())
autos("value").value_counts().sort_index(ascending=False).head(20)
(2357,)
rely 5.000000e+04
imply 9.840044e+03
std 4.811044e+05
min 0.000000e+00
25% 1.100000e+03
50% 2.950000e+03
75% 7.200000e+03
max 1.000000e+08
Title: value, dtype: float64
min 0.000000e+00
max 1.000000e+08
Der Mindestpreis beträgt 0 $ und der Höchstpreis 100.000.000 $. Beides ist verdächtig. Blick auf die tatsächlichen Excessive-Finish-Werte:
99999999 1
27322222 1
12345678 3
Diese Preise liegen weit über der Preisspanne eines typischen oder Excessive-Finish-Fahrzeugs. Blick auf das untere Ende:
0 1421
1 156
Es sind 1.421 Autos für 0 $ gelistet. Obwohl es sich um einen kleinen Bruchteil von 50.000 Zeilen handelt, stellt $0 für unsere Analyse keinen echten Verkaufspreis dar. Wir entfernen Nullen und alles über 350.000 US-Greenback, wo die Preise von plausiblen Excessive-Finish-Werten zu einer kleinen Gruppe von Ausreißern springen.
autos = autos(autos("value").between(1, 351000))
autos("value").describe()
rely 48565.000000
imply 5888.935591
min 1.000000
max 350000.000000
Wir behalten 48.565 Zeilen bei – eine kleine Reduzierung, die Rauschen entfernt, ohne aussagekräftige Daten zu verwerfen.
Lerneinblick: Die Entscheidung, wo der Ausreißerschwellenwert ermittelt werden soll, ist eine der Ermessensentscheidungen bei der Datenbereinigung. Oft gibt es keine einzige richtige Antwort. Wir haben uns für 350.000 US-Greenback entschieden, weil dort die Preise in den Daten von allmählichen Anstiegen zu extremen Werten springen. Dokumentieren Sie Ihre Argumentation überall dort, wo Sie diese Anrufe tätigen, insbesondere wenn Sie Ihre Analyse mit einem Stakeholder teilen.
Schritt 6: Erkunden der Kilometerzählerwerte
autos("odometer_km").value_counts()
150000 32424
125000 5170
100000 2169
...
Title: odometer_km, dtype: int64
Interessanterweise gibt es nur 13 unterschiedliche Werte, obwohl es sich um eine kontinuierliche numerische Variable handelt. Dies liegt daran, dass die Angebotsoberfläche von eBay wahrscheinlich über eine Dropdown-Auswahl und nicht über ein Freitextfeld verfügte, sodass Verkäufer aus voreingestellten Meilenbereichen auswählen konnten. Das ist kein Fehler – es bedeutet nur, dass wir weniger detailliert sind, als wir vielleicht erwarten. Die 13 unterschiedlichen Werte sind sauber und interpretierbar.
Schritt 7: Reinigung des Registrierungsjahres
autos("registration_year").describe()
min 1000.000000
max 9999.000000
Das Jahr 1000 liegt etwa 900 Jahre vor dem Automobil. Das Jahr 9999 liegt 8.000 Jahre in der Zukunft. Wir müssen auf einen realistischen Bereich filtern.
(~autos("registration_year").between(1900, 2016)).sum() / autos.form(0)
0.038793
Nur etwa 4 % der Zeilen liegen außerhalb unseres gültigen Bereichs von 1900 bis 2016 (dem Erfassungsjahr des Datensatzes). Das ist klein genug, um es problemlos entfernen zu können.
autos = autos(autos("registration_year").between(1900, 2016))
autos("registration_year").value_counts(normalize=True).head(10)
2000 0.067608
2005 0.062895
1999 0.062060
Die Verteilung sieht genau so aus, wie man es für einen Gebrauchtwagenmarkt erwarten würde: Die meisten Fahrzeuge wurden in den letzten 20 Jahren zugelassen.
Schritt 8: Preis nach Marke analysieren
. Mit den bereinigten Daten können wir nun die gängigsten Automarken in den Auflistungen vergleichen und sehen, wie sich ihre Durchschnittspreise unterscheiden.
autos("model").value_counts(normalize=True)
volkswagen 0.211264
bmw 0.110045
opel 0.107581
mercedes_benz 0.096463
audi 0.086566
ford 0.069900
...
Volkswagen dominiert mit rund 21 % der Einträge. Deutsche Hersteller (Volkswagen, BMW, Opel, Mercedes-Benz, Audi) machen zusammen etwa die Hälfte aller Einträge aus.
Viele Marken im hinteren Bereich haben einen Anteil von weniger als 1 %. Für eine aussagekräftige Preisanalyse konzentrieren wir uns auf Marken mit mehr als 5 % der Gesamteinträge.
brand_counts = autos("model").value_counts(normalize=True)
common_brands = brand_counts(brand_counts > .05).index
print(common_brands)
Index(('volkswagen', 'bmw', 'opel', 'mercedes_benz', 'audi', 'ford'), dtype='object')
Jetzt berechnen wir den Durchschnittspreis für jede dieser Marken.
brand_mean_prices = {}
for model in common_brands:
brand_only = autos(autos("model") == model)
mean_price = brand_only("value").imply()
brand_mean_prices(model) = int(mean_price)
brand_mean_prices
{'volkswagen': 5402,
'bmw': 8332,
'opel': 2975,
'mercedes_benz': 8628,
'audi': 9336,
'ford': 3749}
Es zeichnet sich ein klares Muster ab: Audi, BMW und Mercedes-Benz liegen im Bereich zwischen 8.000 und 9.300 US-Greenback. Ford und Opel liegen im Bereich zwischen 3.000 und 3.700 US-Greenback. Volkswagen liegt dazwischen bei 5.400 $.
Schritt 9: Vergleich der Kilometerleistung verschiedener Marken
Entspricht ein höherer Durchschnittspreis einer höheren Kilometerleistung? Finden wir es heraus.
brand_mean_mileage = {}
for model in common_brands:
brand_only = autos(autos("model") == model)
mean_mileage = brand_only("odometer_km").imply()
brand_mean_mileage(model) = int(mean_mileage)
mean_mileage = pd.Collection(brand_mean_mileage).sort_values(ascending=False)
mean_prices = pd.Collection(brand_mean_prices).sort_values(ascending=False)
brand_info = pd.DataFrame(mean_mileage, columns=('mean_mileage'))
brand_info("mean_price") = mean_prices
brand_info
mean_mileage mean_price
bmw 132572 8332
mercedes_benz 130788 8628
opel 129310 2975
audi 129157 9336
volkswagen 128707 5402
ford 124266 3749
Die Laufleistung liegt zwischen den Marken in einem relativ engen Bereich (ungefähr 124.000 bis 133.000 km), während die Preise zwischen dem günstigsten (Opel für ca. 3.000 $) und dem teuersten (Audi für ca. 9.300 $) um mehr als das Dreifache variieren. BMW und Mercedes-Benz haben die höchste Laufleistung und erzielen auch hohe Preise, was darauf hindeutet, dass Käufer für diese Marken unabhängig vom Verschleiß einen Aufpreis zahlen.
Lerneinblick: Wenn Sie mehrere Metriken gruppenübergreifend vergleichen, lassen sich Muster viel leichter erkennen, wenn Sie sie nebeneinander in einem einzigen DataFrame platzieren, als wenn Sie zwei separate Ausgaben lesen. Hier zeigt die Kombination, dass die Kilometerleistung nicht der Hauptfaktor für den Preisunterschied zwischen deutschen Luxusmarken und preisgünstigen Optionen ist.
Zusammenfassung der Ergebnisse
Nachdem der Datensatz von 50.000 Zeilen auf etwa 46.900 gültige Einträge bereinigt wurde, sind die wichtigsten Erkenntnisse:
Der Gebrauchtwagenmarkt bei eBay Kleinanzeigen wird von deutschen Marken dominiert, wobei allein Volkswagen 21 % der Angebote ausmacht. Unter den gängigen Marken gibt es einen deutlichen Preisunterschied: Audi, BMW und Mercedes-Benz durchschnittlich 8.000 bis 9.300 US-Greenback professional Fahrzeug, während Ford und Opel durchschnittlich 3.000 bis 3.700 US-Greenback professional Fahrzeug haben. Volkswagen liegt dazwischen, was seine Beliebtheit als Mittelklasse-Possibility erklären könnte.
Die durchschnittliche Kilometerleistung variiert von Marke zu Marke kaum (alle innerhalb von 10 % voneinander), was bedeutet, dass die Kilometerleistung allein die Preisunterschiede nicht erklärt. Das Markenprestige und das typische Marktsegment, das diese Hersteller besetzen, sind stärkere Treiber.
Nächste Schritte
Dieser Datensatz kann noch viel mehr unterstützen:
Analysieren Sie die Preise auf Modellebene. Wir haben uns die Marke angesehen, aber die mannequin In der Spalte können Sie tiefer gehen. Unterscheidet sich der Preis deutlich zwischen einem BMW 3er und einem BMW 7er? Gibt es bestimmte Volkswagen-Modelle in höheren oder niedrigeren Preisklassen?
Segmentieren Sie nach Kilometerbereichen. Der Kilometerzähler zeigt uns bereits 13 saubere Eimer. Sinken die Durchschnittspreise mit zunehmender Kilometerleistung stetig oder ist die Beziehung markenabhängig?
Entdecken Sie nicht reparierte Schäden. Der unrepaired_damage Spalte sagt uns, ob ein Auto einen Schaden hat, der nicht behoben wurde. Sind beschädigte Autos deutlich günstiger? Welcher Anteil der Einträge jeder Marke weist Schäden auf?
Bereinigen und verwenden Sie die Datumsspalten. Wir haben übersprungen date_crawled, ad_createdUnd last_seen für diese Komplettlösung. Die Analyse, wie lange Einträge aktiv blieben, bevor sie entfernt wurden, könnte Aufschluss darüber geben, welche Marken oder Preisklassen sich am schnellsten verkaufen.
Ressourcen
Teilen Sie Ihre erweiterte Analyse in der Neighborhood und taggen Sie @Anna_strahl. Es gibt mehrere interessante Richtungen für dieses Projekt, und es lohnt sich immer, zu sehen, welche Muster andere in den Daten finden.
