Tolles Tutorial zur Verarbeitung von Python-Sportdaten



In den letzten Jahren in meiner Freizeit habe ich Triathlon gespielt. Dieser Sport ist in vielen Ländern der Welt sehr beliebt, insbesondere in den USA, Australien und Europa. Derzeit gewinnt es in Russland und den GUS-Ländern rasch an Popularität. Es geht darum, Amateure einzubeziehen, keine Profis. Im Gegensatz zum morgendlichen Schwimmen im Pool, Radfahren und Joggen beinhaltet der Triathlon die Teilnahme an Wettkämpfen und die systematische Vorbereitung darauf, auch ohne ein Profi zu sein. Sicherlich gibt es unter Ihren Freunden bereits mindestens einen „Eisenmann“ oder jemanden, der vorhat, einer zu werden. Massivität, eine Vielzahl von Entfernungen und Bedingungen, drei Sportarten in einer - all dies hat das Potenzial, eine große Datenmenge zu bilden. Jedes Jahr finden weltweit mehrere hundert Triathlon-Wettbewerbe statt, an denen mehrere hunderttausend Menschen teilnehmen.Die Wettbewerbe werden von mehreren Veranstaltern durchgeführt. Jeder von ihnen veröffentlicht die Ergebnisse natürlich für sich. Aber für Athleten aus Russland und einigen GUS-Ländern ist das Teamtristats.ru sammelt alle Ergebnisse an einem Ort - auf seiner gleichnamigen Website. Dies macht es sehr bequem, nach Ergebnissen zu suchen, sowohl nach Ihren als auch nach Ihren Freunden und Rivalen oder sogar nach Ihren Idolen. Für mich gab es aber auch die Möglichkeit, eine Vielzahl von Ergebnissen programmatisch zu analysieren. Auf trilife veröffentlichte Ergebnisse: lesen .

Dies war mein erstes Projekt dieser Art, da ich erst vor kurzem im Prinzip mit der Datenanalyse und der Verwendung von Python begonnen habe. Daher möchte ich Sie über die technische Umsetzung dieser Arbeit informieren, zumal dabei verschiedene Nuancen aufgetaucht sind, die manchmal einen besonderen Ansatz erfordern. Es geht um Verschrotten, Parsen, Casting von Typen und Formaten, Wiederherstellen unvollständiger Daten, Erstellen einer repräsentativen Stichprobe, Visualisierung, Vektorisierung und sogar paralleles Rechnen.

Die Lautstärke stellte sich als groß heraus, so dass ich alles in fünf Teile zerlegte, damit ich die Informationen dosieren und mich daran erinnern konnte, wo ich nach der Pause anfangen sollte.

Bevor Sie fortfahren, ist es besser, zuerst meinen Artikel mit den Ergebnissen der Studie zu lesen, da hier im Wesentlichen die Küche für ihre Erstellung beschrieben wurde. Es dauert 10-15 Minuten.

Hast du gelesen? Dann lass uns gehen!

Teil 1. Scraping und Parsing


Gegeben: Website tristats.ru . Es gibt zwei Arten von Tabellen, die uns interessieren. Dies ist eigentlich eine Übersichtstabelle aller Rennen und ein Protokoll der Ergebnisse der einzelnen Rennen.





Die Hauptaufgabe bestand darin, diese Daten programmgesteuert abzurufen und zur weiteren Verarbeitung zu speichern. So kam es, dass ich zu dieser Zeit noch keine Erfahrung mit Webtechnologien hatte und daher nicht sofort wusste, wie das geht. Ich habe dementsprechend mit dem begonnen, was ich wusste - schauen Sie sich den Seitencode an. Dies kann mit der rechten Maustaste oder der Taste F12 erfolgen .



Das Menü in Chrome enthält zwei Optionen: Seitencode anzeigen und Code anzeigen . Nicht die offensichtlichste Trennung. Natürlich geben sie unterschiedliche Ergebnisse. Derjenige, der den Code anzeigt, es ist genau das gleiche wie F12 - die direkt textuelle HTML- Darstellung dessen, was im Browser angezeigt wird, ist elementweise.



Das Anzeigen des Seitencodes gibt wiederum den Quellcode der Seite an. Auch HTML , aber es gibt dort keine Daten, nur die Namen der JS-Skripte, die sie entladen. Okay.



Jetzt müssen wir verstehen, wie man Python verwendet, um den Code jeder Seite als separate Textdatei zu speichern. Ich versuche das:

import requests

r = requests.get(url='http://tristats.ru/')
print(r.content)

Und ich bekomme ... den Quellcode. Aber ich brauche das Ergebnis seiner Ausführung. Nachdem ich studiert, gesucht und nachgefragt hatte, stellte ich fest, dass ich ein Tool zur Automatisierung von Browseraktionen brauchte, zum Beispiel Selen . Ich habe es gesagt. Und auch ChromeDriver für die Arbeit mit Google Chrome . Dann habe ich es wie folgt benutzt:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service

service = Service(r'C:\ChromeDriver\chromedriver.exe')
service.start()
driver = webdriver.Remote(service.service_url)
driver.get('http://www.tristats.ru/')
print(driver.page_source)
driver.quit()

Dieser Code öffnet ein Browserfenster und öffnet eine Seite mit der angegebenen URL. Als Ergebnis erhalten wir bereits HTML- Code mit den gewünschten Daten. Aber es gibt einen Haken. Das Ergebnis sind nur 100 Einsendungen und die Gesamtzahl der Rennen beträgt fast 2000. Wie? Tatsache ist, dass zunächst nur die ersten 100 Einträge im Browser angezeigt werden und nur dann, wenn Sie zum Ende der Seite scrollen, die nächsten 100 geladen werden und so weiter. Daher ist es notwendig, das Scrollen programmgesteuert zu implementieren. Verwenden Sie dazu den folgenden Befehl:

driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

Und bei jedem Bildlauf prüfen wir, ob sich der Code der geladenen Seite geändert hat oder nicht. Wenn es sich nicht geändert hat, überprüfen wir mehrmals die Zuverlässigkeit, z. B. 10, dann wird die gesamte Seite geladen und Sie können anhalten. Zwischen den Schriftrollen setzen wir das Zeitlimit auf eine Sekunde, damit die Seite Zeit zum Laden hat. (Auch wenn sie keine Zeit hat, haben wir eine Reserve - weitere neun Sekunden)

Und der vollständige Code sieht folgendermaßen aus:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import time

service = Service(r'C:\ChromeDriver\chromedriver.exe')
service.start()
driver = webdriver.Remote(service.service_url)
driver.get('http://www.tristats.ru/')
prev_html = ''
scroll_attempt = 0

while scroll_attempt < 10:
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(1)

    if prev_html == driver.page_source:
        scroll_attempt += 1
    else:
        prev_html = driver.page_source
        scroll_attempt = 0 

with open(r'D:\tri\summary.txt', 'w') as f:
    f.write(prev_html)

driver.quit()

Wir haben also eine HTML- Datei mit einer Übersichtstabelle aller Rennen. Müssen es analysieren. Verwenden Sie dazu die lxml- Bibliothek .

from lxml import html

Zuerst finden wir alle Zeilen der Tabelle. Um das Vorzeichen einer Zeichenfolge zu bestimmen, sehen Sie sich einfach die HTML- Datei in einem Texteditor an. Dies



kann beispielsweise "tr ng-repeat = 'r in RaceData' class = 'ng-scope'" oder ein Fragment sein, das in keinem Tag mehr gefunden werden kann.

with open(r'D:\tri\summary.txt', 'r') as f:
    sum_html = f.read()

tree = html.fromstring(sum_html)
rows = tree.findall(".//*[@ng-repeat='r in racesData']")

Dann starten wir den Pandas-Datenrahmen und jedes Element jeder Zeile der Tabelle wird in diesen Datenrahmen geschrieben.

import pandas as pd

rs = pd.DataFrame(columns=['date','name','link','males','females','rus','total'], index=range(len(rows))) #rs – races summary

Um herauszufinden, wo jedes einzelne Element versteckt ist, müssen Sie nur den HTML- Code eines der Elemente unserer Zeilen im selben Texteditor anzeigen.

<tr ng-repeat="r in racesData" class="ng-scope">
  <td class="ng-binding">2015-04-26</td>
    <td>
      <img src="/Images/flags/24/USA.png" class="flag">
      <a href="/rus/result/ironman/texas/half/2015" target="_self" class="ng-binding">Ironman Texas 70.3 2015</a>
    </td>
    <td>
      <a href="/rus/result/ironman/texas/half/2015?sex=F" target="_self" class="ng-binding">605</a>
      <i class="fas fa-venus fa-lg" style="color:Pink"></i>
      /
      <a href="/rus/result/ironman/texas/half/2015?sex=M" target="_self" class="ng-binding">1539</a>
      <i class="fas fa-mars fa-lg" style="color:LightBlue"></i>
    </td>
    <td class="ng-binding">
      <img src="/Images/flags/24/rus.png" class="flag">
      <!-- ngIf: r.CountryCount == 0 -->
      <!-- ngIf: r.CountryCount > 0 --><a ng-if="r.CountryCount > 0" href="/rus/result/ironman/texas/half/2015?country=rus" target="_self" class="ng-binding ng-scope">2</a>
      <!-- end ngIf: r.CountryCount > 0 -->
      / 2144
  </td>
</tr>

Der einfachste Weg zur Hardcode-Navigation für Kinder ist, dass es nicht viele von ihnen gibt.

for i in range(len(rows)):
    rs.loc[i,'date'] = rows[i].getchildren()[0].text.strip()
    rs.loc[i,'name'] = rows[i].getchildren()[1].getchildren()[1].text.strip()
    rs.loc[i,'link'] = rows[i].getchildren()[1].getchildren()[1].attrib['href'].strip()
    rs.loc[i,'males'] = rows[i].getchildren()[2].getchildren()[2].text.strip()
    rs.loc[i,'females'] = rows[i].getchildren()[2].getchildren()[0].text.strip()
    rs.loc[i,'rus'] = rows[i].getchildren()[3].getchildren()[3].text.strip()
    rs.loc[i,'total'] = rows[i].getchildren()[3].text_content().split('/')[1].strip()

Hier ist das Ergebnis: Speichern Sie diesen Datenrahmen in einer Datei. Ich benutze Gurke , aber es könnte CSV oder etwas anderes sein.
dateeventlinkmalesfemalesrustotal
02020-07-02Ironman Dubai Duathlon 70.3 2020/rus/result/ironman/dubai-duathlon/half/2020835215651050
12020-02-07Ironman Dubai 70.3 2020/rus/result/ironman/dubai/half/202063813255770
22020-01-29Israman Half 2020/rus/result/israman/israman/half/20206701264796
32019-12-08Ironman Indian Wells La Quinta 70.3 2019/rus/result/ironman/indian-wells-la-quinta/hal...159059362183
42019-12-07Ironman Taupo 70.3 2019/rus/result/ironman/taupo/half/201976742031187
........................
19171994-07-02ITU European Championship Eichstatt Olympic 1994/rus/result/itu/european-championship-eichstat...610261
19181993-09-04Challenge Almere-Amsterdam Long 1993/rus/result/challenge/almere-amsterdam/full/1993795321827
19191993-07-04ITU European Cup Echternach Olympic 1993/rus/result/itu/european-cup-echternach/olympi...600260
19201992-09-12ITU World Championship Huntsville Olympic 1992/rus/result/itu/world-championship-huntsville/...31703317
19211990-09-15ITU World Championship Orlando Olympic 1990/rus/result/itu/world-championship-orlando/oly...2860528



import pickle as pkl

with open(r'D:\tri\summary.pkl', 'wb') as f:
    pkl.dump(df,f)

Zu diesem Zeitpunkt sind alle Daten vom Typ einer Zeichenfolge. Wir werden später konvertieren. Das Wichtigste, was wir jetzt brauchen, sind Links. Wir werden sie zum Scraping von Protokollen aller Rassen verwenden. Wir machen es nach dem Bild und der Ähnlichkeit, wie es für den Pivot-Tisch gemacht wurde. Im Zyklus für alle Rennen für jedes Rennen öffnen wir die Seite als Referenz, scrollen und erhalten den Seitencode. In der Übersichtstabelle finden Sie Informationen zur Gesamtzahl der Teilnehmer am Rennen - insgesamtWir werden es verwenden, um zu verstehen, bis zu welchem ​​Punkt Sie weiter scrollen müssen. Zu diesem Zweck ermitteln wir beim Scraping jeder Seite direkt die Anzahl der Datensätze in der Tabelle und vergleichen sie mit dem erwarteten Gesamtwert. Sobald es gleich ist, haben wir bis zum Ende gescrollt und Sie können mit dem nächsten Rennen fortfahren. Wir haben auch eine Zeitüberschreitung von 60 Sekunden eingestellt. Aß während dieser Zeit, wir kommen nicht zur Summe , gehen zum nächsten Rennen. Der Seitencode wird in einer Datei gespeichert. Wir speichern die Dateien aller Rennen in einem Ordner und benennen sie nach dem Namen der Rennen, dh nach dem Wert in der Ereignisspalte in der Übersichtstabelle. Um einen Namenskonflikt zu vermeiden, müssen alle Rassen unterschiedliche Namen in der Pivot-Tabelle haben. Überprüfen Sie dies:

df[df.duplicated(subset = 'event', keep=False)]

dateeventlinkmalesfemalesrustotal
4502018-07-15A1 Sprint 2018/rus/result/a1/cc/sprint/2018-07-1543154758
4832018-06-23A1 Sprint 2018/rus/result/a1/cc/sprint/2018-06-2361157676
6702017-07-303Grom Olympic 2017/rus/result/3grom//olympic/2017-07-3024944293293
7522017-06-113Grom Olympic 2017/rus/result/3grom//olympic/2017-06-1125128279279
Nun, in der Übersichtstabelle gibt es Wiederholungen, außerdem die Daten und die Anzahl der Teilnehmer ( Männer, Frauen, Rus, Total ), und die Links sind unterschiedlich. Sie müssen diese Protokolle überprüfen, es gibt nur wenige, damit Sie dies manuell tun können. Jetzt sind alle Namen eindeutig, wir starten einen großen Bergbauzyklus:



dateeventlinkmalesfemalesrustotal
4502018-07-15A1 Sprint 7 2018/rus/result/a1/cc/sprint/2018-07-1543154758
4832018-06-23A1 Sprint 6 2018/rus/result/a1/cc/sprint/2018-06-2361157676
6702017-07-303Grom Olympic 7 2017/rus/result/3grom//olympic/2017-07-3024944293293
7522017-06-113Grom Olympic 6 2017/rus/result/3grom//olympic/2017-06-112512827927


service.start()
driver = webdriver.Remote(service.service_url)
timeout = 60

for index, row in df.iterrows():
    try:
        driver.get('http://www.tristats.ru' + row['link'])     
        start = time.time()

        while True:
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            time.sleep(1)
            race_html = driver.page_source
            tree = html.fromstring(race_html)
            race_rows = tree.findall(".//*[@ng-repeat='r in resultsData']")

            if len(race_rows) == int(row['total']):
                break
            if time.time() - start > timeout:
                print('timeout')
                break

        with open(os.path.join(r'D:\tri\races', row['event'] +  '.txt'), 'w') as f:
            f.write(race_html)

    except:
        traceback.print_exc()

    time.sleep(1)
    
driver.quit()

Dies ist ein langer Prozess. Aber wenn alles eingerichtet ist und sich dieser schwere Mechanismus dreht und nacheinander Datendateien hinzufügt, entsteht ein Gefühl angenehmer Aufregung. Pro Minute werden nur etwa drei Protokolle sehr langsam geladen. Links für die Nacht drehen. Es dauerte ungefähr 10 Stunden. Am Morgen wurden die meisten Protokolle hochgeladen. Wie es normalerweise bei der Arbeit mit einem Netzwerk der Fall ist, schlagen einige fehl. Fuhr schnell mit einem zweiten Versuch fort.



Wir haben also 1.922 Dateien mit einer Gesamtkapazität von fast 3 GB. Cool! Das Handling von fast 300 Rennen endete jedoch mit einer Auszeit. Was ist da los? Bei selektiver Überprüfung stellt sich heraus, dass der Gesamtwert aus der Pivot-Tabelle und die Anzahl der von uns überprüften Einträge im Rennprotokoll möglicherweise nicht übereinstimmen. Das ist traurig, weil nicht klar ist, was der Grund für diese Diskrepanz ist. Entweder liegt dies an der Tatsache, dass nicht jeder fertig wird, oder an einem Fehler in der Datenbank. Im Allgemeinen ist das erste Signal der Datenunvollkommenheit. In jedem Fall überprüfen wir diejenigen, bei denen die Anzahl der Einträge 100 oder 0 beträgt. Dies sind die verdächtigsten Kandidaten. Es waren acht von ihnen. Laden Sie sie erneut unter Kontrolle herunter. Übrigens gibt es in zwei von ihnen tatsächlich 100 Einträge.

Nun, wir haben alle Daten. Wir gehen zum Parsen über. Wieder werden wir in einem Zyklus jedes Rennen durchlaufen, die Datei lesen und den Inhalt in einem Pandas DataFrame speichern . Wir werden diese Datenrahmen zu einem Diktat kombinieren , in dem die Namen der Rennen die Schlüssel sind - das heißt, die Ereigniswerte aus der Pivot-Tabelle oder die Namen der Dateien mit dem HTML- Code der Rennseiten stimmen überein.

rd = {} #rd – race details

for e in rs['event']:
    place = []
    ... sex = [], name=..., country, group, place_in_group, swim, t1, bike, t2, run
    result = []

    with open(os.path.join(r'D:\tri\races', e + '.txt'), 'r')
        race_html = f.read()

    tree = html.fromstring(race_html)
    rows = tree.findall(".//*[@ng-repeat='r in resultsData']")

    for j in range(len(rows)):
        row = rows[j]
        parts = row.text_content().split('\n')
        parts = [r.strip() for r in parts if r.strip() != '']
        place.append(parts[0])

        if len([a for a in row.findall('.//i')]) > 0:
            sex.append([a for a in row.findall('.//i')][0].attrib['ng-if'][10:-1])
        else:
            sex.append('')

        name.append(parts[1])

        if len(parts) > 10:
            country.append(parts[2].strip())         
            k=0
        else:
            country.append('')
            k=1

        group.append(parts[3-k])
        ... place_in_group.append(...), swim.append ..., t1, bike, t2, run
        result.append(parts[10-k])

    race = pd.DataFrame()
    race['place'] = place
    ... race['sex'] = sex, race['name'] = ..., 'country', 'group', 'place_in_group', 'swim', ' t1', 'bike', 't2', 'run'
    race['result'] = result

    rd[e] = race

with open(r'D:\tri\details.pkl', 'wb') as f:
    pkl.dump(rd,f)

placesexnamecountrygroupplace in groupswimt1biket2runresult
01MReed, TimAUSMPRO124:341:072:13:461:491:23:174:04:33
12MVan Berkel, TimAUSMPRO224:341:052:13:471:531:27:174:08:36
23MBaldwin, NicholasSEYMPRO326:310:592:14:061:541:25:364:09:06
34MPolizzi, AlexanderAUSMPRO423:211:122:14:531:541:31:164:12:36
45MChang, Chia-HaoTWNM18-24125:181:342:23:382:131:29:014:21:44
56MRondy, GuillaumeFRAM35-39127:511:262:21:532:291:35:194:28:58
67FSteffen, CarolineSUIFPRO126:521:012:24:542:101:34:174:29:14
78MBetten, SamAUSMPRO523:301:262:18:241:571:45:074:30:24
89MGallot, SimonFRAM30-34127:501:332:20:152:131:45:224:37:13
.......................................
524525MSantos, AlfredoPHIM65-69250:424:233:52:1010:323:36:118:33:58
525526FEscober, EulaPHIF18-24547:073:504:43:443:412:59:458:38:07
526527MBelen, Virgilio Jr.PHIM45-497647:055:493:48:1811:213:46:068:38:39
527528MKunimoto, KilhakGUMM70-74240:322:503:53:376:454:01:368:45:20
528529MSumicad, SiegfredPHIM50-545459:104:384:11:556:353:23:458:46:03
529530MGomez, PaulPHIM45-497750:026:294:07:587:243:41:418:53:34
530531MRamos, John RaymundPHIM25-292643:443:044:21:135:563:45:108:59:07
531532FDe Guzman, Clouie AnnePHIF30-34952:293:164:03:027:013:56:399:02:27
532533FSamson, Maria DoloresPHIF45-491748:564:214:16:346:263:47:069:03:23
533534MSalazar, RichardPHIM40-4410742:194:024:30:366:393:39:519:03:27
Neben der Tabelle mit den Ergebnissen der Teilnehmer enthält die HTML- Datei jedes Rennens auch Datum, Name und Ort des Wettbewerbs. Das Datum und der Name befinden sich bereits in der Pivot-Tabelle, es gibt jedoch keinen Speicherort. Wir lesen diese Informationen aus HTML- Dateien und fügen sie einer neuen Spalte in der Pivot-Tabelle hinzu.

for index, row in rs.iterrows():
    e = row['event']       
    with open(os.path.join(r'D:\tri\races', e + '.txt'), 'r') as f:
        race_html = f.read()

    tree = html.fromstring(race_html)
    header_elem = [tb for tb in tree.findall('.//tbody') if tb.getchildren()[0].getchildren()[0].text == ''][0]
    location = header_elem.getchildren()[1].getchildren()[1].text.strip()
    rs.loc[index, 'loc'] = location

eventdatelocmalefemalerustotallink
0Ironman Dubai Duathlon 70.3 20202020-07-02Dubai, United Arab Emirates835215651050...
1Ironman Dubai 70.3 20202020-02-07Dubai, United Arab Emirates63813255770...
2Israman Half 20202020-01-29Israel, Eilat6701264796...
3Ironman Indian Wells La Quinta 70.3 20192019-12-08Indian Wells/La Quinta, California, USA159059362183...
4Ironman Taupo 70.3 20192019-12-07New Zealand76742031187...
5Ironman Bahrain 70.3 20192019-12-07Manama, Bahrain858214381072...
6Ironman Western Australia 20192019-12-01Busselton, Western Australia94022911169...
7Ironman Mar del Plata 20192019-12-01Mar del Plata, Argentina506663572...
8Ironman Cozumel 20192019-11-24Cozumel, Mexico1158395121553...
9Ironman Arizona 20192019-11-24Tempe, Arizona, USA169763332330...
10Ironman Xiamen 70.3 20192019-11-10Xiamen, China897170141067...
Speichern. In eine neue Datei.

with open(r'D:\tri\summary1.pkl', 'wb') as f:
    pkl.dump(df,f)

Teil 2. Geben Sie Casting und Formatierung ein


Also haben wir alle Daten heruntergeladen und in die Datenrahmen eingefügt. Alle Werte sind jedoch vom Typ str . Dies gilt für das Datum, die Ergebnisse, den Ort und alle anderen Parameter. Alle Parameter müssen in die entsprechenden Typen konvertiert werden.

Beginnen wir mit der Pivot-Tabelle.
eventdatelocmalefemalerustotallink
0Ironman Dubai Duathlon 70.3 20202020-07-02Dubai, United Arab Emirates835215651050...
1Ironman Dubai 70.3 20202020-02-07Dubai, United Arab Emirates63813255770...
2Israman Half 20202020-01-29Israel, Eilat6701264796...
3Ironman Indian Wells La Quinta 70.3 20192019-12-08Indian Wells/La Quinta, California, USA159059362183...
4Ironman Taupo 70.3 20192019-12-07New Zealand76742031187...
5Ironman Bahrain 70.3 20192019-12-07Manama, Bahrain858214381072...
6Ironman Western Australia 20192019-12-01Busselton, Western Australia94022911169...
7Ironman Mar del Plata 20192019-12-01Mar del Plata, Argentina506663572...
8Ironman Cozumel 20192019-11-24Cozumel, Mexico1158395121553...
9Ironman Arizona 20192019-11-24Tempe, Arizona, USA169763332330...
10Ironman Xiamen 70.3 20192019-11-10Xiamen, China897170141067...
...........................

Datum (und Uhrzeit


Ereignis , Ort und Link bleiben unverändert . Datum Konvertit Pandas datetime wie folgt:

rs['date'] = pd.to_datetime(rs['date'])

Der Rest wird in einen ganzzahligen Typ umgewandelt:

cols = ['males', 'females', 'rus', 'total']
rs[cols] = rs[cols].astype(int)

Alles verlief reibungslos, es traten keine Fehler auf. Also alles in Ordnung - speichern:

with open(r'D:\tri\summary2.pkl', 'wb') as f:
    pkl.dump(rs, f)

Jetzt Rennen Datenrahmen. Da alle Rennen bequemer und schneller auf einmal und nicht einzeln verarbeitet werden können, werden sie mithilfe der concat- Methode in einem großen ar- Datenrahmen (kurz für alle Datensätze ) zusammengefasst .

ar = pd.concat(rd) 

ar enthält 1.416.365 Einträge.

Konvertieren Sie nun place und place in group in einen ganzzahligen Wert.

ar[['place', 'place in group']] = ar[['place', 'place in group']].astype(int))

Als nächstes verarbeiten wir die Spalten mit temporären Werten. Wir werden sie in der Art Timedelta von Pandas gießen . Damit die Konvertierung jedoch erfolgreich ist, müssen Sie die Daten ordnungsgemäß vorbereiten. Sie können sehen, dass einige Werte, die weniger als eine Stunde betragen, ohne Angabe des Tippes vergehen. Müssen Sie es hinzufügen.
placesexnamecountrygroupplace in groupswimt1biket2runresult
01MDejan PatrcevicCROM40-44129:032:502:09:171:371:22:064:04:51
12MLukas KrpecCZEM35-39129:002:402:07:011:481:25:484:06:15
23MMarin KoceicCROM40-44227:342:092:12:131:301:27:194:10:44


for col in ['swim', 't1', 'bike', 't2', 'run', 'result']:
    strlen = ar[col].str.len()
    ar.loc[strlen==5, col] = '0:' + ar.loc[strlen==5, col]
    ar.loc[strlen==4, col] = '0:0' + ar.loc[strlen==4, col]

Jetzt sehen die verbleibenden Zeichenfolgen folgendermaßen aus : In Timedelta konvertieren :
placesexnamecountrygroupplace in groupswimt1biket2runresult
01MDejan PatrcevicCROM40-4410:29:030:02:502:09:170:01:371:22:064:04:51
12MLukas KrpecCZEM35-3910:29:000:02:402:07:010:01:481:25:484:06:15
23MMarin KoceicCROM40-4420:27:340:02:092:12:130:01:301:27:194:10:44


for col in ['swim', 't1', 'bike', 't2', 'run', 'result']:
    ar[col] = pd.to_timedelta(ar[col])

Fußboden


Mach weiter. Überprüfen Sie, ob in der Spalte Geschlecht nur die Werte von M und F angegeben sind :

ar['sex'].unique() 

Out: ['M', 'F', '']

Tatsächlich gibt es immer noch eine leere Zeichenfolge, dh das Geschlecht ist nicht angegeben. Mal sehen, wie viele solcher Fälle:

len(ar[ar['sex'] == '']) 

Out: 2538

Nicht so viel ist gut. In Zukunft werden wir versuchen, diesen Wert weiter zu reduzieren. Lassen Sie in der Zwischenzeit die Geschlechtsspalte in Form von Linien. Wir werden das Ergebnis speichern, bevor wir zu ernsthafteren und riskanteren Transformationen übergehen. Um die Kontinuität zwischen den Dateien aufrechtzuerhalten, transformieren wir den kombinierten Datenrahmen ar zurück in das Wörterbuch der Datenrahmen rd :

for event in ar.index.get_level_values(0).unique():
    rd[event] = ar.loc[event]

with open(r'D:\tri\details1.pkl', 'wb') as f:
    pkl.dump(rd,f)

Übrigens verringerten sich die Dateigrößen aufgrund der Konvertierung der Typen einiger Spalten von 367 KB auf 295 KB für die Pivot-Tabelle und von 251 MB auf 168 MB für Rennprotokolle.

Landesvorwahl


Jetzt schauen wir uns das Land an.

ar['country'].unique()

Out: ['CRO', 'CZE', 'SLO', 'SRB', 'BUL', 'SVK', 'SWE', 'BIH', 'POL', 'MK', 'ROU', 'GRE', 'FRA', 'HUN', 'NOR', 'AUT', 'MNE', 'GBR', 'RUS', 'UAE', 'USA', 'GER', 'URU', 'CRC', 'ITA', 'DEN', 'TUR', 'SUI', 'MEX', 'BLR', 'EST', 'NED', 'AUS', 'BGI', 'BEL', 'ESP', 'POR', 'UKR', 'CAN', 'IRL', 'JPN', 'HKG', 'JEY', 'SGP', 'BRA', 'QAT', 'LUX', 'RSA', 'NZL', 'LAT', 'PHI', 'KSA', 'SEY', 'MAS', 'OMA', 'ARG', 'ECU', 'THA', 'JOR', 'BRN', 'CIV', 'FIN', 'IRN', 'BER', 'LBA', 'KUW', 'LTU', 'SRI', 'HON', 'INA', 'LBN', 'PAN', 'EGY', 'MLT', 'WAL', 'ISL', 'CYP', 'DOM', 'IND', 'VIE', 'MRI', 'AZE', 'MLD', 'LIE', 'VEN', 'ALG', 'SYR', 'MAR', 'KZK', 'PER', 'COL', 'IRQ', 'PAK', 'CZK', 'KAZ', 'CHN', 'NEP', 'ISR', 'MKD', 'FRO', 'BAN', 'ARU', 'CPV', 'ALB', 'BIZ', 'TPE', 'KGZ', 'BNN', 'CUB', 'SNG', 'VTN', 'THI', 'PRG', 'KOR', 'RE', 'TW', 'VN', 'MOL', 'FRE', 'AND', 'MDV', 'GUA', 'MON', 'ARM', 'F.I.TRI.', 'BAHREIN', 'SUECIA', 'REPUBLICA CHECA', 'BRASIL', 'CHI', 'MDA', 'TUN', 'NDL', 'Danish(Dane)', 'Welsh', 'Austrian', 'Unknown', 'AFG', 'Argentinean', 'Pitcairn', 'South African', 'Greenland', 'ESTADOS UNIDOS', 'LUXEMBURGO', 'SUDAFRICA', 'NUEVA ZELANDA', 'RUMANIA', 'PM', 'BAH', 'LTV', 'ESA', 'LAB', 'GIB', 'GUT', 'SAR', 'ita', 'aut', 'ger', 'esp', 'gbr', 'hun', 'den', 'usa', 'sui', 'slo', 'cze', 'svk', 'fra', 'fin', 'isr', 'irn', 'irl', 'bel', 'ned', 'sco', 'pol', 'SMR', 'mex', 'STEEL T BG', 'KINO MANA', 'IVB', 'TCH', 'SCO', 'KEN', 'BAS', 'ZIM', 'Joe', 'PUR', 'SWZ', 'Mark', 'WLS', 'MYA', 'BOT', 'REU', 'NAM', 'NCL', 'BOL', 'GGY', 'ISV', 'TWN', 'GUM', 'FIJ', 'COK', 'NGR', 'IRI', 'GAB', 'ANT', 'GEO', 'COG', 'sue', 'SUD', 'BAR', 'CAY', 'BO', 'VE', 'AX', 'MD', 'PAR', 'UM', 'SEN', 'NIG', 'RWA', 'YEM', 'PLE', 'GHA', 'ITU', 'UZB', 'MGL', 'MAC', 'DMA', 'TAH', 'TTO', 'AHO', 'JAM', 'SKN', 'GRN', 'PRK', 'NFK', 'SOL', 'Sandy', 'SAM', 'PNG', 'SGS', 'Suchy, Jorg', 'SOG', 'GEQ', 'BVT', 'DJI', 'CHA', 'ANG', 'YUG', 'IOT', 'HAI', 'SJM', 'CUW', 'BHU', 'ERI', 'FLK', 'HMD', 'GUF', 'ESH', 'sandy', 'UMI', 'selsmark, 'Alise', 'Eddie', '31/3, Colin', 'CC', '', '', '', '', '', ' ', '', '', '', '-', '', 'GRL', 'UGA', 'VAT', 'ETH', 'ASA', 'PYF', 'ATA', 'ALA', 'MTQ', 'ZZ', 'CXR', 'AIA', 'TJK', 'GUY', 'KR', 'PF', 'BN', 'MO', 'LA', 'CAM', 'NCA', 'ZAM', 'MAD', 'TOG', 'VIR', 'ATF', 'VAN', 'SLE', 'GLP', 'SCG', 'LAO', 'IMN', 'BUR', 'IR', 'SY', 'CMR', 'GBS', 'SUR', 'MOZ', 'BLM', 'MSR', 'CAF', 'BEN', 'COD', 'CCK', 'TUV', 'TGA', 'GI', 'XKX', 'NRU', 'NC', 'LBR', 'TAN', 'VIN', 'SSD', 'GP', 'PS', 'IM', 'JE', '', 'MLI', 'FSM', 'LCA', 'GMB', 'MHL', 'NH', 'FL', 'CT', 'UT', 'AQ', 'Korea', 'Taiwan', 'NewCaledonia', 'Czech Republic', 'PLW', 'BRU', 'RUN', 'NIU', 'KIR', 'SOM', 'TKM', 'SPM', 'BDI', 'COM', 'TCA', 'SHN', 'DO2', 'DCF', 'PCN', 'MNP', 'MYT', 'SXM', 'MAF', 'GUI', 'AN', 'Slovak republic', 'Channel Islands', 'Reunion', 'Wales', 'Scotland', 'ica', 'WLF', 'D', 'F', 'I', 'B', 'L', 'E', 'A', 'S', 'N', 'H', 'R', 'NU', 'BES', 'Bavaria', 'TLS', 'J', 'TKL', 'Tirol"', 'P', '?????', 'EU', 'ES-IB', 'ES-CT', '', 'SOO', 'LZE', '', '', '', '', '', '']

412 eindeutige Werte.

Grundsätzlich wird ein Land durch einen dreistelligen Buchstabencode in Großbuchstaben angegeben. Aber anscheinend nicht immer. Tatsächlich gibt es eine internationale Norm ISO 3166 , in der für alle Länder, auch für diejenigen, die nicht mehr existieren, die entsprechenden dreistelligen und zweistelligen Codes vorgeschrieben sind. Für Python befindet sich eine der Implementierungen dieses Standards im Pycountry- Paket . So funktioniert das:

import pycountry as pyco

pyco.countries.get(alpha_3 = 'RUS')

Out: Country(alpha_2='RU', alpha_3='RUS', name='Russian Federation', numeric='643')

Daher werden wir alle dreistelligen Codes überprüfen, die zu Großbuchstaben führen und eine Antwort in Ländern.get (...) und historischem_Land.get (...) geben :

valid_a3 = [c for c in ar['country'].unique() if pyco.countries.get(alpha_3 = c.upper()) != None or pyco.historic_countries.get(alpha_3 = c.upper()) != None])

Es gab 190 von 412 von ihnen, das heißt weniger als die Hälfte.
Für den restlichen 222 (wir ihre Liste bezeichnen Tofix ), werden wir einen erstellen fix passenden Wörterbuch , in dem der Schlüssel wird der ursprüngliche Name, und den Wert ist ein dreistelliger Code gemäß der ISO - Norm.

tofix = list(set(ar['country'].unique()) - set(valid_a3))

Überprüfen Sie zunächst die zweistelligen Codes mit pycountry.countries.get (alpha_2 = ...) , was zu Großbuchstaben führt:

for icc in tofix: #icc -invalid country code
    if pyco.countries.get(alpha_2 = icc.upper()) != None:
        fix[icc] = pyco.countries.get(alpha_2 = icc.upper()).alpha_3
    else:
        if pyco.historic_countries.get(alpha_2 = icc.upper()) != None:
            fix[icc] = pyco.historic_countries.get(alpha_2 = icc.upper()).alpha_3

Dann die vollständigen Namen durch pycountry.countries.get (name = ...), pycountry.countries.get (common_name = ...) und führen sie zur Form str.title () :

for icc in tofix:
    if pyco.countries.get(common_name = icc.title()) != None:
        fix[icc] = pyco.countries.get(common_name = icc.title()).alpha_3
    else:
        if pyco.countries.get(name = icc.title()) != None:
            fix[icc] = pyco.countries.get(name = icc.title()).alpha_3
        else:
            if pyco.historic_countries.get(name = icc.title()) != None:
                fix[icc] = pyco.historic_countries.get(name = icc.title()).alpha_3

Daher reduzieren wir die Anzahl der nicht erkannten Werte auf 190. Immer noch ziemlich viel: Sie werden feststellen, dass es unter ihnen immer noch viele dreistellige Codes gibt, aber dies ist keine ISO. Was dann? Es stellt sich heraus, dass es einen anderen Standard gibt - Olympia . Leider ist seine Implementierung nicht in Pycountry enthalten und Sie müssen nach etwas anderem suchen. Die Lösung wurde in Form einer CSV- Datei auf datahub.io gefunden . Platzieren Sie den Inhalt dieser Datei in einem Pandas DataFrame namens cdf . ioc - Internationales Olympisches Komitee (IOC)
['URU', '', 'PAR', 'SUECIA', 'KUW', 'South African', '', 'Austrian', 'ISV', 'H', 'SCO', 'ES-CT', ', 'GUI', 'BOT', 'SEY', 'BIZ', 'LAB', 'PUR', ' ', 'Scotland', '', '', 'TCH', 'TGA', 'UT', 'BAH', 'GEQ', 'NEP', 'TAH', 'ica', 'FRE', 'E', 'TOG', 'MYA', '', 'Danish (Dane)', 'SAM', 'TPE', 'MON', 'ger', 'Unknown', 'sui', 'R', 'SUI', 'A', 'GRN', 'KZK', 'Wales', '', 'GBS', 'ESA', 'Bavaria', 'Czech Republic', '31/3, Colin', 'SOL', 'SKN', '', 'MGL', 'XKX', 'WLS', 'MOL', 'FIJ', 'CAY', 'ES-IB', 'BER', 'PLE', 'MRI', 'B', 'KSA', '', '', 'LAT', 'GRE', 'ARU', '', 'THI', 'NGR', 'MAD', 'SOG', 'MLD', '?????', 'AHO', 'sco', 'UAE', 'RUMANIA', 'CRO', 'RSA', 'NUEVA ZELANDA', 'KINO MANA', 'PHI', 'sue', 'Tirol"', 'IRI', 'POR', 'CZK', 'SAR', 'D', 'BRASIL', 'DCF', 'HAI', 'ned', 'N', 'BAHREIN', 'VTN', 'EU', 'CAM', 'Mark', 'BUL', 'Welsh', 'VIN', 'HON', 'ESTADOS UNIDOS', 'I', 'GUA', 'OMA', 'CRC', 'PRG', 'NIG', 'BHU', 'Joe', 'GER', 'RUN', 'ALG', '', 'Channel Islands', 'Reunion', 'REPUBLICA CHECA', 'slo', 'ANG', 'NewCaledonia', 'GUT', 'VIE', 'ASA', 'BAR', 'SRI', 'L', '', 'J', 'BAS', 'LUXEMBURGO', 'S', 'CHI', 'SNG', 'BNN', 'den', 'F.I.TRI.', 'STEEL T BG', 'NCA', 'Slovak republic', 'MAS', 'LZE', '-', 'F', 'BRU', '', 'LBA', 'NDL', 'DEN', 'IVB', 'BAN', 'Sandy', 'ZAM', 'sandy', 'Korea', 'SOO', 'BGI', '', 'LTV', 'selsmark, Alise', 'TAN', 'NED', '', 'Suchy, Jorg', 'SLO', 'SUDAFRICA', 'ZIM', 'Eddie', 'INA', '', 'SUD', 'VAN', 'FL', 'P', 'ITU', 'ZZ', 'Argentinean', 'CHA', 'DO2', 'WAL']


official nameshort nameiso2iso3ioc
0NaNTaiwanTWTWNTPE
1AfghanistanAfghanistanAFAFGAFG
2AlbaniaAlbaniaALALBALB
3AlgeriaAlgeriaDZDZAALG
4American SamoaAmerican SamoaASASMASA
5AndorraAndorraADANDAND
6AngolaAngolaAOAGOANG
7AnguillaAnguillaAIAIAAIA
8AntarcticaAntarcticaAQATANaN
9Antigua and BarbudaAntigua & BarbudaAGATGANT
10ArgentinaArgentinaARARGARG


len(([x for x in tofix if x.upper() in list(cdf['ioc'])]))

Out: 82

Unter den dreistelligen Codes von tofix wurden 82 entsprechende IOCs gefunden. Fügen Sie sie unserem passenden Wörterbuch hinzu.

for icc in tofix:
    if icc.upper() in list(cdf['ioc']):
        ind = cdf[cdf['ioc'] == icc.upper()].index[0]
        fix[icc] = cdf.loc[ind, 'iso3']

108 Rohwerte übrig. Sie werden manuell beendet und wenden sich manchmal an Google, um Hilfe zu erhalten. Aber selbst die manuelle Steuerung löst das Problem nicht vollständig. Es bleiben 49 Werte übrig, die bereits nicht zu interpretieren sind. Die meisten dieser Werte sind wahrscheinlich nur Datenfehler.
{'BGI': 'BRB', 'WAL': 'GBR', 'MLD': 'MDA', 'KZK': 'KAZ', 'CZK': 'CZE', 'BNN': 'BEN', 'SNG': 'SGP', 'VTN': 'VNM', 'THI': 'THA', 'PRG': 'PRT', 'MOL': 'MDA', 'FRE': 'FRA', 'F.I.TRI.': 'ITA', 'BAHREIN': 'BHR', 'SUECIA': 'SWE', 'REPUBLICA CHECA': 'CZE', 'BRASIL': 'BRA', 'NDL': 'NLD', 'Danish (Dane)': 'DNK', 'Welsh': 'GBR', 'Austrian': 'AUT', 'Argentinean': 'ARG', 'South African': 'ZAF', 'ESTADOS UNIDOS': 'USA', 'LUXEMBURGO': 'LUX', 'SUDAFRICA': 'ZAF', 'NUEVA ZELANDA': 'NZL', 'RUMANIA': 'ROU', 'sco': 'GBR', 'SCO': 'GBR', 'WLS': 'GBR', '': 'IND', '': 'IRL', '': 'ARM', '': 'BGR', '': 'SRB', ' ': 'BLR', '': 'GBR', '': 'FRA', '': 'HND', '-': 'CRI', '': 'AZE', 'Korea': 'KOR', 'NewCaledonia': 'FRA', 'Czech Republic': 'CZE', 'Slovak republic': 'SVK', 'Channel Islands': 'FRA', 'Reunion': 'FRA', 'Wales': 'GBR', 'Scotland': 'GBR', 'Bavaria': 'DEU', 'Tirol"': 'AUT', '': 'KGZ', '': 'BLR', '': 'BLR', '': 'BLR', '': 'RUS', '': 'BLR', '': 'RUS'}



unfixed = [x for x in tofix if x not in fix.keys()]

Out: ['', 'H', 'ES-CT', 'LAB', 'TCH', 'UT', 'TAH', 'ica', 'E', 'Unknown', 'R', 'A', '31/3, Colin', 'XKX', 'ES-IB','B','SOG','?????','KINO MANA','sue','SAR','D', 'DCF', 'N', 'EU', 'Mark', 'I', 'Joe', 'RUN', 'GUT', 'L', 'J', 'BAS', 'S', 'STEEL T BG', 'LZE', 'F', 'Sandy', 'DO2', 'sandy', 'SOO', 'LTV', 'selsmark, Alise', 'Suchy, Jorg' 'Eddie', 'FL', 'P', 'ITU', 'ZZ']

Diese Schlüssel haben eine leere Zeichenfolge im passenden Wörterbuch.

for cc in unfixed:
    fix[cc] = ''

Schließlich fügen wir den passenden Wörterbuchcodes hinzu, die gültig, aber in Kleinbuchstaben geschrieben sind.

for cc in valid_a3:
    if cc.upper() != cc:
        fix[cc] = cc.upper()

Jetzt ist es Zeit, die gefundenen Ersetzungen anzuwenden. Um die anfänglichen Daten für einen weiteren Vergleich zu speichern, kopieren Sie die Länderspalte in das Land "raw" . Anschließend korrigieren wir mithilfe des erstellten übereinstimmenden Wörterbuchs die Werte in der Länderspalte , die nicht der ISO entsprechen.

for cc in fix:
    ind = ar[ar['country'] == cc].index
    ar.loc[ind,'country'] = fix[cc]

Hier kann man natürlich nicht auf Vektorisierung verzichten, die Tabelle hat fast eineinhalb Millionen Zeilen. Aber laut Wörterbuch machen wir einen Zyklus, aber wie sonst? Überprüfen Sie, wie viele Datensätze geändert wurden:

len(ar[ar['country'] != ar['country raw']])

Out: 315955

das sind mehr als 20% der Gesamtmenge.

ar[ar['country'] != ar['country raw']].sample(10)

placesexnamecountrygroupplace in group...country raw
285286MAlbaek, Mads OrlaDNKM30-3463...DEN
12881289MBenthien, AndreasDEUM40-44198...GER
490491MLontok, JoselitoPHLM50-5418...PHI
145146MMathiasen, KeldDNKM45-4916...DEN
445446MPalm, FrancoisZAFM25-2948...RSA
152153MMuller, JohannesDEUM35-3919...GER
764765FWoscher SylviaDEUF55-598...GER
21822183MKojellis, HolgerDEUM40-44258...GER
12931294MZweer, WaldemarDEUM25-29117...GER
747748MPetersen, MathiasDNKM25-2979...DE

len(ar[ar['country'] == ''])

Out: 3221

Dies ist die Anzahl der Datensätze ohne Land oder mit einem informellen Land. Die Anzahl der einzelnen Länder ist von 412 auf 250 gesunken. Hier sind sie: Jetzt gibt es keine Abweichungen mehr. Wir speichern das Ergebnis in einer neuen Datei details2.pkl , nachdem wir den kombinierten Datenrahmen wie zuvor wieder in ein Wörterbuch mit Datenrahmen konvertiert haben.
['', 'ABW', 'AFG', 'AGO', 'AIA', 'ALA', 'ALB', 'AND', 'ANT', 'ARE', 'ARG', 'ARM', 'ASM', 'ATA', 'ATF', 'AUS', 'AUT', 'AZE', 'BDI', 'BEL', 'BEN', 'BES', 'BGD', 'BGR', 'BHR', 'BHS', 'BIH', 'BLM', 'BLR', 'BLZ', 'BMU', 'BOL', 'BRA', 'BRB', 'BRN', 'BTN', 'BUR', 'BVT', 'BWA', 'CAF', 'CAN', 'CCK', 'CHE', 'CHL', 'CHN', 'CIV', 'CMR', 'COD', 'COG', 'COK', 'COL', 'COM', 'CPV', 'CRI', 'CTE', 'CUB', 'CUW', 'CXR', 'CYM', 'CYP', 'CZE', 'DEU', 'DJI', 'DMA', 'DNK', 'DOM', 'DZA', 'ECU', 'EGY', 'ERI', 'ESH', 'ESP', 'EST', 'ETH', 'FIN', 'FJI', 'FLK', 'FRA', 'FRO', 'FSM', 'GAB', 'GBR', 'GEO', 'GGY', 'GHA', 'GIB', 'GIN', 'GLP', 'GMB', 'GNB', 'GNQ', 'GRC', 'GRD', 'GRL', 'GTM', 'GUF', 'GUM', 'GUY', 'HKG', 'HMD', 'HND', 'HRV', 'HTI', 'HUN', 'IDN', 'IMN', 'IND', 'IOT', 'IRL', 'IRN', 'IRQ', 'ISL', 'ISR', 'ITA', 'JAM', 'JEY', 'JOR', 'JPN', 'KAZ', 'KEN', 'KGZ', 'KHM', 'KIR', 'KNA', 'KOR', 'KWT', 'LAO', 'LBN', 'LBR', 'LBY', 'LCA', 'LIE', 'LKA', 'LTU', 'LUX', 'LVA', 'MAC', 'MAF', 'MAR', 'MCO', 'MDA', 'MDG', 'MDV', 'MEX', 'MHL', 'MKD', 'MLI', 'MLT', 'MMR', 'MNE', 'MNG', 'MNP', 'MOZ', 'MSR', 'MTQ', 'MUS', 'MYS', 'MYT', 'NAM', 'NCL', 'NER', 'NFK', 'NGA', 'NHB', 'NIC', 'NIU', 'NLD', 'NOR', 'NPL', 'NRU', 'NZL', 'OMN', 'PAK', 'PAN', 'PCN', 'PER', 'PHL', 'PLW', 'PNG', 'POL', 'PRI', 'PRK', 'PRT', 'PRY', 'PSE', 'PYF', 'QAT', 'REU', 'ROU', 'RUS', 'RWA', 'SAU', 'SCG', 'SDN', 'SEN', 'SGP', 'SGS', 'SHN', 'SJM', 'SLB', 'SLE', 'SLV', 'SMR', 'SOM', 'SPM', 'SRB', 'SSD', 'SUR', 'SVK', 'SVN', 'SWE', 'SWZ', 'SXM', 'SYC', 'SYR', 'TCA', 'TCD', 'TGO', 'THA', 'TJK', 'TKL', 'TKM', 'TLS', 'TON', 'TTO', 'TUN', 'TUR', 'TUV', 'TWN', 'TZA', 'UGA', 'UKR', 'UMI', 'URY', 'USA', 'UZB', 'VAT', 'VCT', 'VEN', 'VGB', 'VIR', 'VNM', 'VUT', 'WLF', 'WSM', 'YEM', 'YUG', 'ZAF', 'ZMB', 'ZWE']



Standort


Denken Sie nun daran, dass die Erwähnung von Ländern auch in der Pivot-Tabelle in der Spalte loc enthalten ist . Es muss auch zu einem Standard-Look gebracht werden. Hier ist eine etwas andere Geschichte: Weder ISO- noch Olympia-Codes sind sichtbar. Alles wird in einer ziemlich freien Form beschrieben. Die Stadt, das Land und andere Komponenten der Adresse werden mit einem Komma und in zufälliger Reihenfolge aufgelistet. Irgendwo an erster Stelle, irgendwo an letzter Stelle. pycountry wird hier nicht helfen. Und es gibt viele Rekorde - für das Rennen 1922 525 einzigartige Orte (in seiner ursprünglichen Form). Aber hier wurde ein geeignetes Werkzeug gefunden. Dies ist Geopy , nämlich der Geolocator Nominatim . Es funktioniert so:
eventdatelocmalesfemalesrustotallink
0Ironman Dubai Duathlon 70.3 20202020-07-02Dubai, United Arab Emirates835215651050
1Ironman Dubai 70.3 20202020-02-07Dubai, United Arab Emirates63813255770
2Israman Half 20202020-01-29Israel, Eilat6701264796
3Ironman Indian Wells La Quinta 70.3 20192019-12-08Indian Wells/La Quinta, California, USA159059362183
4Ironman Taupo 70.3 20192019-12-07New Zealand76742031187
5Ironman Bahrain 70.3 20192019-12-07Manama, Bahrain858214381072
6Ironman Western Australia 20192019-12-01Busselton, Western Australia94022911169
7Ironman Mar del Plata 20192019-12-01Mar del Plata, Argentina506663572
8Ironman Cozumel 20192019-11-24Cozumel, Mexico1158395121553
9Ironman Arizona 20192019-11-24Tempe, Arizona, USA169763332330




from geopy.geocoders import Nominatim

geolocator = Nominatim(user_agent='triathlon results researcher')
geolocator.geocode(' , , ', language='en')

Out: Location( , – , , Altaysky District, Altai Krai, Siberian Federal District, Russia, (51.78897945, 85.73956296106752, 0.0))

Auf Anfrage gibt es in zufälliger Form eine strukturierte Antwortadresse und Koordinaten. Wenn Sie die Sprache wie hier einstellen - Englisch, was es kann - wird übersetzt. Zunächst benötigen wir den Standardnamen des Landes für die spätere Übersetzung in den ISO-Code. Es nimmt nur den letzten Platz in der Adresseigenschaft ein . Da Geolocator jedes Mal eine Anfrage an den Server sendet, ist dieser Vorgang nicht schnell und dauert 500 Minuten für 500 Datensätze. Außerdem kommt es vor, dass die Antwort nicht kommt. In diesem Fall hilft manchmal eine zweite Anfrage. In meiner ersten Antwort kamen nicht zu 130 Anfragen. Die meisten von ihnen wurden mit zwei Wiederholungsversuchen verarbeitet. 34 Namen wurden jedoch auch bei mehreren weiteren Versuchen nicht verarbeitet. Hier sind sie:
['Tongyeong, Korea, Korea, South', 'Constanta, Mamaia, Romania, Romania', 'Weihai, China, China', '. , .', 'Odaiba Marin Park, Tokyo, Japan, Japan', 'Sweden, Smaland, Kalmar', 'Cholpon-Ata city, Resort Center "Kapriz", Kyrgyzstan', 'Luxembourg, Region Moselle, Moselle', 'Chita Peninsula, Japan', 'Kraichgau Region, Germany', 'Jintang, Chengdu, Sichuan Province, China, China', 'Madrid, Spain, Spain', 'North American Pro Championship, St. George, Utah, USA', 'Milan Idroscalo Linate, Italy', 'Dexing, Jiangxi Province, China, China', 'Mooloolaba, Australia, Australia', 'Nathan Benderson Park (NBP), 5851 Nathan Benderson Circle, Sarasota, FL 34235., United States', 'Strathclyde Country Park, North Lanarkshire, Glasgow, Great Britain', 'Quijing, China', 'United States of America , Hawaii, Kohala Coast', 'Buffalo City, East London, South Africa', 'Spain, Vall de Cardener', ', . ', 'Asian TriClub Championship, Hefei, China', 'Taizhou, Jiangsu Province, China, China', ', , «»', 'Buffalo, Gallagher Beach, Furhmann Blvd, United States', 'North American Pro Championship | St. George, Utah, USA', 'Weihai, Shandong, China, China', 'Tarzo - Revine Lago, Italy', 'Lausanee, Switzerland', 'Queenstown, New Zealand, New Zealand', 'Makuhari, Japan, Japan', 'Szombathlely, Hungary']

Es ist zu sehen, dass in vielen Ländern das Land doppelt erwähnt wird, und dies stört tatsächlich. Im Allgemeinen musste ich diese verbleibenden Namen manuell verarbeiten und Standardadressen wurden für alle erhalten. Außerdem habe ich aus diesen Adressen ein Land ausgewählt und dieses Land in eine neue Spalte in der Pivot-Tabelle geschrieben. Da die Arbeit mit Geopy , wie gesagt, nicht schnell ist, habe ich beschlossen, die Standortkoordinaten - Längen- und Breitengrad - sofort zu speichern. Sie werden später zur Visualisierung auf der Karte nützlich sein. Danach suchte Alpha_3 mit pyco.countries.get (name = '...') nach dem Land nach Namen und wies ihm einen dreistelligen Code zu.
eventdateloccountrylatitudelongitude...
0Ironman Dubai Duathlon 70.3 20202020-07-02Dubai, United Arab EmiratesUnited Arab Emirates25.065755.1713...
1Ironman Dubai 70.3 20202020-02-07Dubai, United Arab EmiratesUnited Arab Emirates25.065755.1713...
2Israman Half 20202020-01-29Israel, EilatIsrael29.556934.9498...
3Ironman Indian Wells La Quinta 70.3 20192019-12-08Indian Wells/La Quinta, California, USAUnited States of America33.7238-116.305...
4Ironman Taupo 70.3 20192019-12-07New ZealandNew Zealand-41.5001172.834...
5Ironman Bahrain 70.3 20192019-12-07Manama, BahrainBahrain26.223550.5822...
6Ironman Western Australia 20192019-12-01Busselton, Western AustraliaAustralia-33.6445115.349...
7Ironman Mar del Plata 20192019-12-01Mar del Plata, ArgentinaArgentina-37.9977-57.5483...
8Ironman Cozumel 20192019-11-24Cozumel, MexicoMexico20.4318-86.9203...
9Ironman Arizona 20192019-11-24Tempe, Arizona, USAUnited States of America33.4255-111.94...
10Ironman Xiamen 70.3 20192019-11-10Xiamen, ChinaChina24.4758118.075...

eventdateloccountrylatitudelongitude...
0Ironman Dubai Duathlon 70.3 20202020-07-02Dubai, United Arab EmiratesARE25.065755.1713...
1Ironman Dubai 70.3 20202020-02-07Dubai, United Arab EmiratesARE25.065755.1713...
2Israman Half 20202020-01-29Israel, EilatISR29.556934.9498...
3Ironman Indian Wells La Quinta 70.3 20192019-12-08Indian Wells/La Quinta, California, USAUSA33.7238-116.305...
4Ironman Taupo 70.3 20192019-12-07New ZealandNZL-41.5001172.834...
5Ironman Bahrain 70.3 20192019-12-07Manama, BahrainBHR26.223550.5822...
6Ironman Western Australia 20192019-12-01Busselton, Western AustraliaAUS-33.6445115.349...
7Ironman Mar del Plata 20192019-12-01Mar del Plata, ArgentinaARG-37.9977-57.5483...
8Ironman Cozumel 20192019-11-24Cozumel, MexicoMEX20.4318-86.9203...
9Ironman Arizona 20192019-11-24Tempe, Arizona, USAUSA33.4255-111.94...
10Ironman Xiamen 70.3 20192019-11-10Xiamen, ChinaCHN24.4758118.075...

Entfernung


Eine weitere wichtige Aktion, die am Pivot-Tisch ausgeführt werden muss, ist die Bestimmung der Distanz für jedes Rennen. Dies ist für uns nützlich, um zukünftige Geschwindigkeiten zu berechnen. Beim Triathlon gibt es vier Hauptdistanzen - Sprint, Olympia, Halbeisen und Eisen. Sie können sehen, dass in den Namen der Rennen normalerweise ein Hinweis auf die Entfernung angegeben ist - dies sind Sprint , Olympic , Half , Full Words . Darüber hinaus haben verschiedene Organisatoren ihre eigenen Entfernungsbezeichnungen. Die Hälfte von Ironman wird zum Beispiel als 70,3 bezeichnet - durch die Anzahl der Meilen in der Ferne, die olympische - 5150 durch die Anzahl der Kilometer (51,5), und das Eisen kann als voll bezeichnet werdenoder im Allgemeinen als Mangel an Erklärung - zum Beispiel Ironman Arizona 2019 . Ironman - er ist Eisen! In der Herausforderung wird die Eisenentfernung als lang und die Halbeisenentfernung als mittel bezeichnet . Unser russischer IronStar bedeutet voll wie 226 und halb so groß wie 113 - gemessen an der Anzahl der Kilometer, aber normalerweise sind auch die Wörter Full und Half vorhanden. Wenden Sie nun all dieses Wissen an und markieren Sie alle Rassen gemäß den in den Namen enthaltenen Schlüsselwörtern.

sprints = rs.loc[[i for i in rs.index if 'sprint' in rs.loc[i, 'event'].lower()]]
olympics1 = rs.loc[[i for i in rs.index if 'olympic' in rs.loc[i, 'event'].lower()]]
olympics2 = rs.loc[[i for i in rs.index if '5150' in rs.loc[i, 'event'].lower()]]
olympics = pd.concat([olympics1, olympics2])
#…   

rsd = pd.concat([sprints, olympics, halfs, fulls]) 

In rsd stellte sich heraus, dass es 1 925 Rekorde gab, dh drei mehr als die Gesamtzahl der Rennen, so dass einige unter zwei Kriterien fielen. Schauen wir sie uns an:

rsd[rsd.duplicated(keep=False)]['event'].sort_index()

eventdateloccountrylatitudelongitude...
38Temiradam 113 Half 20192019-09-22,KAZ43.652151.158...
38Temiradam 113 Half 20192019-09-22,KAZ43.652151.158...
65Triway Olympic Sprint 20192019-09-08, --RUS47.221439.7114...
65Triway Olympic Sprint 20192019-09-08, --RUS47.221439.7114...
82Ironman Dun Laoghaire Full Swim 70.3 20192019-08-25Ireland, Dun LaoghaireIRL53.2923-6.13601...
82Ironman Dun Laoghaire Full Swim 70.3 20192019-08-25Ireland, Dun LaoghaireIRL53.2923-6.13601...
Tatsächlich ist es. Im ersten Paar im Namen Temiradam 113 Half 2019 werden sowohl Half als auch 113 erwähnt . Dies ist jedoch kein Widerspruch, beide wurden als Hälften identifiziert. Als nächstes kommt der Triway Olympic Sprint 2019 . Hier kann man wirklich verwirrt sein - es gibt Olympia und Sprint . Sie können es herausfinden, indem Sie sich das Protokoll mit den Ergebnissen des Rennens ansehen. Die beste Zeit ist 1:09. Das ist also ein Sprint. Löschen Sie diesen Eintrag aus der olympischen Liste.
placesexnamecountrygroupplace in groupswimt1biket2runresult
01MRUSM100:12:2100:00:3100:34:1300:00:2500:21:4901:09:19
12MRUSM200:12:2100:00:2800:34:1500:00:2600:23:0701:10:38
23MRUSM300:14:2000:00:3700:35:4800:00:3400:22:1601:13:35


olympics.drop(65)

Wir werden dasselbe mit dem sich kreuzenden Ironman Dun Laoghaire Full Swim 70.3 2019 tun. Hier ist die beste Zeit 4:00. Dies ist typisch für die Hälfte. Löschen Sie den Datensatz mit dem Index 85 aus den vollständigen Werten .
placesexnamecountrygroupplace in groupswimt1biket2runresult
01MBrownlee, AlistairGBRMPRO100:23:1900:02:1802:21:1900:01:5501:11:4204:00:33
12MSmales, ElliotGBRMPRO200:24:4700:02:0902:29:2600:01:4801:12:4704:10:57
23MBowden, AdamGBRMPRO300:23:2400:02:1802:32:0900:02:0601:13:4904:13:46


fulls.drop(85)

Jetzt schreiben wir die Entfernungsinformationen in den Hauptdatenrahmen und sehen, was passiert ist:

rs['dist'] = ''

rs.loc[sprints.index,'dist'] = 'sprint'
rs.loc[olympics.index,'dist'] = 'olympic'
rs.loc[halfs.index,'dist'] = 'half'
rs.loc[fulls.index,'dist'] = 'full'

rs.sample(10)

eventplacesexnamecountrygroupplace in group...country rawgroup raw
...566MVladimir KozarSVKM40-448...SVKMOpen 40-44
...8MHANNES COOLBELMPRO11...BELMPRO M
...445FIleana SodaniUSAF45-494...USAF45-49 F
...227FJARLINSKA BozenaPOLF45-492...POLFK45-49
...440FCeline OrrigoniFRAF40-446...FRAF40-44 F
...325MVladimir EckertSVKM40-446...SVKMOpen 40-44
...139FATRASZKIEWICZ MagdaPOLF40-442...POLFK40-44
...18MMarijn de JongeNLDMPRO18...NEDMpro
...574MLuca AndaloITAM40-449...ITAMOpen 40-44
...67MURBANKIEWICZ AleksandraPOLM35-391...POLMK35-39
Stellen Sie sicher, dass keine ungedeckten Einträge vorhanden sind:

len(rs[rs['dist'] == ''])

Out: 0

Und schauen Sie sich unsere problematischen, mehrdeutigen an:

rs.loc[[38,65,82],['event','dist']]

eventdist
38Temiradam 113 Half 2019half
65Triway Olympic Sprint 2019sprint
82Ironman Dun Laoghaire Full Swim 70.3 2019half
Alles ist gut. In einer neuen Datei speichern:

pkl.dump(rs, open(r'D:\tri\summary5.pkl', 'wb'))

Altersgruppen


Nun zurück zu den Rennprotokollen.

Wir haben bereits Geschlecht, Land und Ergebnisse des Teilnehmers analysiert und auf eine Standardform gebracht. Es blieben jedoch zwei weitere Spalten übrig - die Gruppe und tatsächlich der Name selbst. Beginnen wir mit den Gruppen. Beim Triathlon ist es üblich, die Teilnehmer nach Altersgruppen zu unterteilen. Oft fällt auch eine Gruppe von Fachleuten auf. Tatsächlich ist der Versatz in jeder dieser Gruppen separat - die ersten drei Plätze in jeder Gruppe werden vergeben. In Gruppen wird die Qualifikation für Meisterschaften ausgewählt, zum Beispiel auf Konu.

Kombinieren Sie alle Datensätze und sehen Sie, welche Gruppen im Allgemeinen existieren.

rd = pkl.load(open(r'D:\tri\details2.pkl', 'rb'))
ar = pd.concat(rd)
ar['group'].unique()

Es stellte sich heraus, dass es eine große Anzahl von Gruppen gab - 581. Hundert zufällig ausgewählte Gruppen sehen folgendermaßen aus: Mal sehen, welche von ihnen die zahlreichsten sind:
['MSenior', 'FAmat.', 'M20', 'M65-59', 'F25-29', 'F18-22', 'M75-59', 'MPro', 'F24', 'MCORP M', 'F21-30', 'MSenior 4', 'M40-50', 'FAWAD', 'M16-29', 'MK40-49', 'F65-70', 'F65-70', 'M12-15', 'MK18-29', 'M50up', 'FSEMIFINAL 2 PRO', 'F16', 'MWhite', 'MOpen 25-29', 'F', 'MPT TRI-2', 'M16-24', 'FQUALIFIER 1 PRO', 'F15-17', 'FSEMIFINAL 2 JUNIOR', 'FOpen 60-64', 'M75-80', 'F60-69', 'FJUNIOR A', 'F17-18', 'FAWAD BLIND', 'M75-79', 'M18-29', 'MJUN19-23', 'M60-up', 'M70', 'MPTS5', 'F35-40', "M'S PT1", 'M50-54', 'F65-69', 'F17-20', 'MP4', 'M16-29', 'F18up', 'MJU', 'MPT4', 'MPT TRI-3', 'MU24-39', 'MK35-39', 'F18-20', "M'S", 'F50-55', 'M75-80', 'MXTRI', 'F40-45', 'MJUNIOR B', 'F15', 'F18-19', 'M20-29', 'MAWAD PC4', 'M30-37', 'F21-30', 'Mpro', 'MSEMIFINAL 1 JUNIOR', 'M25-34', 'MAmat.', 'FAWAD PC5', 'FA', 'F50-60', 'FSenior 1', 'M80-84', 'FK45-49', 'F75-79', 'M<23', 'MPTS3', 'M70-75', 'M50-60', 'FQUALIFIER 3 PRO', 'M9', 'F31-40', 'MJUN16-19', 'F18-19', 'M PARA', 'F35-44', 'MParaathlete', 'F18-34', 'FA', 'FAWAD PC2', 'FAll Ages', 'M PARA', 'F31-40', 'MM85', 'M25-39']



ar['group'].value_counts()[:30]

Out:
M40-44 199157
M35-39 183738
M45-49 166796
M30-34 154732
M50-54 107307
M25-29 88980
M55-59 50659
F40-44 48036
F35-39 47414
F30-34 45838
F45-49 39618
MPRO 38445
F25-29 31718
F50-54 26253
M18-24 24534
FPRO 23810
M60-64 20773
M 12799
F55-59 12470
M65-69 8039
F18-24 7772
MJUNIOR 6605
F60-64 5067
M20-24 4580
FJUNIOR 4105
M30-39 3964
M40-49 3319
F 3306
M70-74 3072
F20-24 2522

Sie können sehen, dass dies Gruppen von fünf Jahren sind, getrennt für Männer und getrennt für Frauen, sowie Berufsgruppen MPRO und FPRO .

Unser Standard wird also sein:

ag = ['MPRO', 'M18-24', 'M25-29', 'M30-34', 'M35-39', 'M40-44', 'M45-49', 'M50-54', 'M55-59', 'M60-64',  'M65-69', 'M70-74', 'M75-79', 'M80-84', 'M85-90', 'FPRO', 'F18-24', 'F25-29', 'F30-34', 'F35-39', 'F40-44',   'F45-49', 'F50-54', 'F55-59', 'F60-64', 'F65-69', 'F70-74', 'F75-79', 'F80-84', 'F85-90']
#ag – age group

Dieses Set deckt fast 95% aller Finisher ab.

Natürlich können wir nicht alle Gruppen auf diesen Standard bringen. Aber wir suchen nach denen, die ihnen ähnlich sind und geben zumindest einen Teil davon. Zuerst bringen wir die Großbuchstaben und entfernen die Leerzeichen. Folgendes ist passiert: Konvertieren Sie sie in unsere Standard-Modelle.
['F25-29F', 'F30-34F', 'F30-34-34', 'F35-39F', 'F40-44F', 'F45-49F', 'F50-54F', 'F55-59F', 'FAG:FPRO', 'FK30-34', 'FK35-39', 'FK40-44', 'FK45-49', 'FOPEN50-54', 'FOPEN60-64', 'MAG:MPRO', 'MK30-34', 'MK30-39', 'MK35-39', 'MK40-44', 'MK40-49', 'MK50-59', 'M40-44', 'MM85-89', 'MOPEN25-29', 'MOPEN30-34', 'MOPEN35-39', 'MOPEN40-44', 'MOPEN45-49', 'MOPEN50-54', 'MOPEN70-74', 'MPRO:', 'MPROM', 'M0-44"']



fix = { 'F25-29F': 'F25-29', 'F30-34F' : 'F30-34', 'F30-34-34': 'F30-34', 'F35-39F': 'F35-39', 'F40-44F': 'F40-44', 'F45-49F': 'F45-49', 'F50-54F': 'F50-54', 'F55-59F': 'F55-59', 'FAG:FPRO': 'FPRO', 'FK30-34': 'F30-34',      'FK35-39': 'F35-39', 'FK40-44': 'F40-44', 'FK45-49': 'F45-49', 'FOPEN50-54': 'F50-54', 'FOPEN60-64': 'F60-64', 'MAG:MPRO': 'MPRO', 'MK30-34': 'M30-34', 'MK30-39': 'M30-39', 'MK35-39': 'M35-39', 'MK40-44': 'M40-44', 'MK40-49': 'M40-49', 'MK50-59': 'M50-59', 'M40-44': 'M40-44', 'MM85-89': 'M85-89', 'MOPEN25-29': 'M25-29', 'MOPEN30-34': 'M30-34', 'MOPEN35-39': 'M35-39', 'MOPEN40-44': 'M40-44', 'MOPEN45-49': 'M45-49', 'MOPEN50-54': 'M50-54', 'MOPEN70-74': 'M70- 74', 'MPRO:' :'MPRO', 'MPROM': 'MPRO', 'M0-44"' : 'M40-44'}

Jetzt wenden wir unsere Transformation auf den Hauptdatenrahmen ar an , speichern jedoch zuerst die ursprünglichen Gruppenwerte in der neuen Rohgruppe der Gruppe .

ar['group raw'] = ar['group']

In der Gruppenspalte belassen wir nur die Werte, die unserem Standard entsprechen.

Jetzt können wir unsere Bemühungen würdigen:

len(ar[(ar['group'] != ar['group raw'])&(ar['group']!='')])

Out: 273

Nur ein bisschen auf dem Niveau von eineinhalb Millionen. Aber Sie werden es nicht wissen, bis Sie es versuchen.

Ausgewählte 10 sehen folgendermaßen aus: Speichern Sie die neue Version des Datenrahmens, nachdem Sie ihn wieder in das dritte Wörterbuch konvertiert haben .
eventplacesexnamecountrygroupplace in group...country rawgroup raw
...566MVladimir KozarSVKM40-448...SVKMOpen 40-44
...8MHANNES COOLBELMPRO11...BELMPRO M
...445FIleana SodaniUSAF45-494...USAF45-49 F
...227FJARLINSKA BozenaPOLF45-492...POLFK45-49
...440FCeline OrrigoniFRAF40-446...FRAF40-44 F
...325MVladimir EckertSVKM40-446...SVKMOpen 40-44
...139FATRASZKIEWICZ MagdaPOLF40-442...POLFK40-44
...18MMarijn de JongeNLDMPRO18...NEDMpro
...574MLuca AndaloITAM40-449...ITAMOpen 40-44
...67MURBANKIEWICZ AleksandraPOLM35-391...POLMK35-39


pkl.dump(rd, open(r'D:\tri\details3.pkl', 'wb'))

Name


Nun kümmern wir uns um die Namen. Sehen wir uns selektiv 100 Namen aus verschiedenen Rassen an:

list(ar['name'].sample(100))

Out: ['Case, Christine', 'Van der westhuizen, Wouter', 'Grace, Scott', 'Sader, Markus', 'Schuller, Gunnar', 'Juul-Andersen, Jeppe', 'Nelson, Matthew', ' ', 'Westman, Pehr', 'Becker, Christoph', 'Bolton, Jarrad', 'Coto, Ricardo', 'Davies, Luke', 'Daniltchev, Alexandre', 'Escobar Labastida, Emmanuelle', 'Idzikowski, Jacek', 'Fairaislova Iveta', 'Fisher, Kulani', 'Didenko, Viktor', 'Osborne, Jane', 'Kadralinov, Zhalgas', 'Perkins, Chad', 'Caddell, Martha', 'Lynaire PARISH', 'Busing, Lynn', 'Nikitin, Evgeny', 'ANSON MONZON, ROBERTO', 'Kaub, Bernd', 'Bank, Morten', 'Kennedy, Ian', 'Kahl, Stephen', 'Vossough, Andreas', 'Gale, Karen', 'Mullally, Kristin', 'Alex FRASER', 'Dierkes, Manuela', 'Gillett, David', 'Green, Erica', 'Cunnew, Elliott', 'Sukk, Gaspar', 'Markina Veronika', 'Thomas KVARICS', 'Wu, Lewen', 'Van Enk, W.J.J', 'Escobar, Rosario', 'Healey, Pat', 'Scheef, Heike', 'Ancheta, Marlon', 'Heck, Andreas', 'Vargas Iii, Raul', 'Seferoglou, Maria', 'chris GUZMAN', 'Casey, Timothy', 'Olshanikov Konstantin', 'Rasmus Nerrand', 'Lehmann Bence', 'Amacker, Kirby', 'Parks, Chris', 'Tom, Troy', 'Karlsson, Ulf', 'Halfkann, Dorothee', 'Szabo, Gergely', 'Antipov Mikhail', 'Von Alvensleben, Alvo', 'Gruber, Peter', 'Leblanc, Jean-Philippe', 'Bouchard, Jean-Francois', 'Marchiotto MASSIMO', 'Green, Molly', 'Alder, Christoph', 'Morris, Huw', 'Deceur, Marc', 'Queenan, Derek', 'Krause, Carolin', 'Cockings, Antony', 'Ziehmer Chris', 'Stiene, John', 'Chmet Daniela', 'Chris RIORDAN', 'Wintle, Mel', ' ', 'GASPARINI CHRISTIAN', 'Westbrook, Christohper', 'Martens, Wim', 'Papson, Chris', 'Burdess, Shaun', 'Proctor, Shane', 'Cruzinha, Pedro', 'Hamard, Jacques', 'Petersen, Brett', 'Sahyoun, Sebastien', "O'Connell, Keith", 'Symoshenko, Zhan', 'Luternauer, Jan', 'Coronado, Basil', 'Smith, Alex', 'Dittberner, Felix', 'N?sman, Henrik', 'King, Malisa', 'PUHLMANN Andre']

Alles ist schwierig. Es gibt eine Vielzahl von Optionen für Einträge: Vorname Nachname, Nachname Vorname, Nachname, Vorname, Nachname, Vorname usw. Das heißt, eine andere Reihenfolge, ein anderes Register, irgendwo gibt es ein Trennzeichen - ein Komma. Es gibt auch viele Protokolle, in denen Kyrillisch verwendet wird. Es gibt auch keine Einheitlichkeit, und solche Formate können gefunden werden: "Nachname Vorname", "Vorname Nachname", "Vorname Zweiter Vorname Nachname", "Nachname Vorname Zweiter Vorname". Obwohl in der Tat der zweite Vorname auch in der lateinischen Schreibweise gefunden wird. Und hier tritt übrigens ein weiteres Problem auf - die Transliteration. Es sollte auch beachtet werden, dass selbst wenn es keinen zweiten Vornamen gibt, der Datensatz möglicherweise nicht auf zwei Wörter beschränkt ist. Beispielsweise besteht für Hispanics der Name plus Nachname normalerweise aus drei oder vier Wörtern. Die Holländer haben das Präfix Van, die Chinesen und Koreaner haben auch zusammengesetzte Namen, die normalerweise aus drei Wörtern bestehen. Im Allgemeinen müssen Sie diesen gesamten Rebus irgendwie entwirren und auf das Maximum standardisieren. Innerhalb eines Rennens ist das Namensformat in der Regel für alle gleich, aber auch hier gibt es Fehler, die wir jedoch nicht behandeln werden. Beginnen wir mit dem Speichern der vorhandenen Werte im neuen Spaltennamen raw :

ar['name raw'] = ar['name']

Die überwiegende Mehrheit der Protokolle ist in Latein, daher möchte ich als erstes transliterieren. Mal sehen, welche Zeichen im Namen des Teilnehmers enthalten sein können.

set( ''.join(ar['name'].unique()))

Out: [' ', '!', '"', '#', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|', '\x7f', '\xa0', '¤', '¦', '§', '', '«', '\xad', '', '°', '±', 'µ', '¶', '·', '»', '', 'І', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'є', 'і', 'ў', '–', '—', '‘', '’', '‚', '“', '”', '„', '†', '‡', '…', '‰', '›', '']

Was gibt es nur! Zusätzlich zu den tatsächlichen Buchstaben und Leerzeichen gibt es noch eine Reihe verschiedener ausgefallener Charaktere. Von diesen können der Punkt '.', Der Bindestrich '-' und der Apostroph '' 'als gültig angesehen werden, dh nicht versehentlich vorhanden sein. Außerdem wurde festgestellt, dass in vielen deutschen und norwegischen Vor- und Nachnamen ein Fragezeichen "?" Sie ersetzen anscheinend die Zeichen aus dem erweiterten lateinischen Alphabet - '?', 'A', 'o', 'u' ,? und andere. Hier einige Beispiele: Das Komma ist, obwohl es sehr häufig vorkommt, nur ein Trennzeichen, das bei bestimmten Rassen verwendet wird, sodass es auch in die Kategorie der inakzeptablen fällt. Zahlen sollten auch nicht in Namen erscheinen.
Pierre-Alexandre Petit, Jean-louis Lafontaine, Faris Al-Sultan, Jean-Francois Evrard, Paul O'Mahony, Aidan O'Farrell, John O'Neill, Nick D'Alton, Ward D'Hulster, Hans P.J. Cami, Luis E. Benavides, Maximo Jr. Rueda, Prof. Dr. Tim-Nicolas Korf, Dr. Boris Scharlowsk, Eberhard Gro?mann, Magdalena Wei?, Gro?er Axel, Meyer-Szary Krystian, Morten Halkj?r, RASMUSSEN S?ren Balle



bs = [s for s in symbols if not (s.isalpha() or s in " . - ' ? ,")] #bs – bad symbols

bs

Out: ['!', '"', '#', '&', '(', ')', '*', '+', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '>', '@', '[', '\\', ']', '^', '_', '`', '|', '\x7f', '\xa0', '¤', '¦', '§', '', '«', '\xad', '', '°', '±', '¶', '·', '»', '–', '—', '‘', '’', '‚', '“', '”', '„', '†', '‡', '…', '‰', '›', '']

Wir werden alle diese Zeichen vorübergehend entfernen, um herauszufinden, wie viele Einträge vorhanden sind:

for s in bs:
    ar['name'] = ar['name'].str.replace(s, '')

corr = ar[ar['name'] != ar['name raw']]

Es gibt 2.184 solcher Datensätze, das sind nur 0,15% der Gesamtzahl - sehr wenige. Schauen wir uns 100 davon an:

list(corr['name raw'].sample(100))

Out: ['Scha¶ffl, Ga?nter', 'Howard, Brian &', 'Chapiewski, Guilherme (Gc)', 'Derkach 1svd_mail_ru', 'Parker H1 Lauren', 'Leal le?n, Yaneri', 'TencA, David', 'Cortas La?pez, Alejandro', 'Strid, Bja¶rn', '(Crutchfield) Horan, Katie', 'Vigneron, Jean-Michel.Vigneron@gmail.Com', '\xa0', 'Telahr, J†rgen', 'St”rmer, Melanie', 'Nagai B1 Keiji', 'Rinc?n, Mariano', 'Arkalaki, Angela (Evangelia)', 'Barbaro B1 Bonin Anna G:Charlotte', 'Ra?esch, Ja¶rg', "CAVAZZI NICCOLO\\'", 'D„nzel, Thomas', 'Ziska, Steffen (Gerhard)', 'Kobilica B1 Alen', 'Mittelholcz, Bala', 'Jimanez Aguilar, Juan Antonio', 'Achenza H1 Giovanni', 'Reppe H2 Christiane', 'Filipovic B2 Lazar', 'Machuca Ka?hnel, Ruban Alejandro', 'Gellert (Silberprinz), Christian', 'Smith (Guide), Matt', 'Lenatz H1 Benjamin', 'Da¶llinger, Christian', 'Mc Carthy B1 Patrick Donnacha G:Bryan', 'Fa¶llmer, Chris', 'Warner (Rivera), Lisa', 'Wang, Ruijia (Ray)', 'Mc Carthy B1 Donnacha', 'Jones, Nige (Paddy)', 'Sch”ler, Christoph', '\xa0', 'Holthaus, Adelhard (Allard)', 'Mi;Arro, Ana', 'Dr: Koch Stefan', '\xa0', '\xa0', 'Ziska, Steffen (Gerhard)', 'Albarraca\xadn Gonza?lez, Juan Francisco', 'Ha¶fling, Imke', 'Johnston, Eddie (Edwin)', 'Mulcahy, Bob (James)', 'Gottschalk, Bj”rn', '\xa0', 'Gretsch H2 Kendall', 'Scorse, Christopher (Chris)', 'Kiel‚basa, Pawel', 'Kalan, Magnus', 'Roderick "eric" SIMBULAN', 'Russell;, Mark', 'ROPES AND GRAY TEAM 3', 'Andrade, H?¦CTOR DANIEL', 'Landmann H2 Joshua', 'Reyes Rodra\xadguez, Aithami', 'Ziska, Steffen (Gerhard)', 'Ziska, Steffen (Gerhard)', 'Heuza, Pierre', 'Snyder B1 Riley Brad G:Colin', 'Feldmann, Ja¶rg', 'Beveridge H1 Nic', 'FAGES`, perrine', 'Frank", Dieter', 'Saarema¤el, Indrek', 'Betancort Morales, Arida–y', 'Ridderberg, Marie_Louise', '\xa0', 'Ka¶nig, Johannes', 'W Van(der Klugt', 'Ziska, Steffen (Gerhard)', 'Johnson, Nick26', 'Heinz JOHNER03', 'Ga¶rg, Andra', 'Maruo B2 Atsuko', 'Moral Pedrero H1 Eva Maria', '\xa0', 'MATUS SANTIAGO Osc1r', 'Stenbrink, Bja¶rn', 'Wangkhan, Sm1.Thaworn', 'Pullerits, Ta¶nu', 'Clausner, 8588294149', 'Castro Miranda, Josa Ignacio', 'La¶fgren, Pontuz', 'Brown, Jann ( Janine )', 'Ziska, Steffen (Gerhard)', 'Koay, Sa¶ren', 'Ba¶hm, Heiko', 'Oleksiuk B2 Vita', 'G Van(de Grift', 'Scha¶neborn, Guido', 'Mandez, A?lvaro', 'Garca\xada Fla?rez, Daniel']

Nach langem Nachforschen wurde beschlossen, alle alphabetischen Zeichen sowie ein Leerzeichen, einen Bindestrich, einen Apostroph und ein Fragezeichen durch ein Komma, einen Punkt und ein Symbol '\ xa0' zu ersetzen und alle anderen Zeichen durch eine leere Zeichenfolge zu ersetzen. das heißt, einfach löschen.

ar['name'] = ar['name raw']

for s in symbols:
    if s.isalpha() or s in " - ? '":        
        continue        
    if s in ".,\xa0":
        ar['name'] = ar['name'].str.replace(s, ' ')       
    else:
        ar['name'] = ar['name'].str.replace(s, '')

Dann entfernen Sie zusätzliche Leerzeichen:

ar['name'] = ar['name'].str.split().str.join(' ')
ar['name'] = ar['name'].str.strip() #   

Lass uns nachsehen, was passiert ist:

ar.loc[corr.index].sample(10)

placesexnamecountry...name raw
6364MCurzillat B MARANO Annouck GJulieFRA...Curzillat B1 MARANO Annouck G:Julie
425426MNaranjo Quintero CndidoESP...Naranjo Quintero, C‡ndido
13471348FChang Margaret PeggyUSA...Chang, Margaret (Peggy)
790791MGonzalez RubenPRI...Gonzalez`, Ruben
15621563MGarcia Hernandez EliasMEX...Garcia Hernandez/, Elias
5051MReppe H ChristianeDEU...Reppe H2 Christiane
528529MHo ShihkenTWN...Ho, Shih—ken
819820MElmously A R AbdelrahmanEGY...Elmously, A.R. (Abdelrahman)
249250Fboyer IsabelleTHA...`boyer, Isabelle
744745MGarcaa Morales Pedro LucianoESP...Garca¬a Morales, Pedro Luciano
Es wurde auch festgestellt, dass es Namen gibt, die ausschließlich aus Fragezeichen bestehen.

qmon = ar[(ar['name'].str.replace('?', '').str.strip() == '')&(ar['name']!='')] #qmon – question mark only names

Es gibt 3.429 davon. Es sieht ungefähr so ​​aus: Unser Ziel, Namen auf den gleichen Standard zu bringen, ist es, dass dieselben Namen gleich aussehen, aber auf unterschiedliche Weise unterschiedlich sind. Bei Namen, die nur aus Fragezeichen bestehen, unterscheiden sie sich nur in der Anzahl der Zeichen. Dies gibt jedoch nicht die volle Sicherheit, dass Namen mit derselben Nummer wirklich gleich sind. Daher ersetzen wir sie alle durch eine leere Zeichenfolge und werden in Zukunft nicht mehr berücksichtigt.
placesexnamecountrygroupplace in group...country rawgroup rawname raw
818819M???? ???JPNM45-49177...JPNM45-49????, ???
11011102M?? ??JPNM50-54159...JPNM50-54??, ??
162163M? ??CHNM30-3422...CHNM30-34?, ??
12711272F???? ????JPNF50-5415...JPNF50-54????, ????
552553M??? ??JPNM25-2930...JPNM25-29???, ??
423424M??? ????JPNM55-5924...JPNM55-59???, ????
936937F?? ??JPNF50-547...JPNF50-54??, ??
244245M? ??KORM50-5430...KORM50-54?, ??
627628M? ?CHNM40-4494...CHNM40-44?, ?
194195M?????? ?????RUS188...RUSM?????? ?????


ar.loc[qmon.index, 'name'] = ''

Die Gesamtzahl der Einträge, bei denen der Name die leere Zeichenfolge ist, beträgt 3.454. Nicht so sehr - wir werden überleben. Nachdem wir unnötige Zeichen entfernt haben, können wir mit der Transliteration fortfahren. Um dies zu tun, bringen Sie zuerst alles in Kleinbuchstaben, um nicht doppelt zu arbeiten.

ar['name'] = ar['name'].str.lower()

Erstellen Sie als Nächstes ein Wörterbuch:

trans = {'':'a', '':'b', '':'v', '':'g', '':'d', '':'e', '':'e', '':'zh', '':'z',  '':'i', '':'y', '':'k', '':'l', '':'m', '':'n', '':'o', '':'p', '':'r', '':'s', '':'t', '':'u', '':'f', '':'kh', '':'ts', '':'ch', '':'sh', '':'shch', '':'', '':'y', '':'', '':'e', '':'yu', '':'ya', 'є':'e', 'і': 'i','ў':'w','µ':'m'}

Es enthielt auch Buchstaben aus dem sogenannten erweiterten kyrillischen Alphabet - 'є', 'і', 'ў' , die in den belarussischen und ukrainischen Sprachen verwendet werden, sowie den griechischen Buchstaben 'µ' . Wenden Sie die Transformation an:

for s in trans:
    ar['name'] = ar['name'].str.replace(s, trans[s])

Aus dem funktionierenden Kleinbuchstaben übersetzen wir nun alles in das vertraute Format, wobei der Vor- und Nachname mit einem Großbuchstaben beginnt:

ar['name'] = ar['name'].str.title()

Lass uns nachsehen, was passiert ist.

ar[ar['name raw'].str.lower().str[0].isin(trans.keys())].sample(10)

placesexname...country rawname raw
99100MNikolay Golovkin...RUS
9596MMaksim Vasilevich Chubakov...RUS
325326FGanieva Aygul...RUS
661662MMaksut Nizamutdinov...RUS
356357FKolobanova Svetlana...RUS
117118MGuskov Vladislav...RUS
351352MKolesnikov Dmitriy...RUS
9293MKuznetsov Oleg...RUS
5051MKhoraykin Maksim...RUS
67MBrylev Aleksey...RUS
Überprüfen Sie abschließend die eindeutigen Zeichen:

set( ''.join(ar['name'].unique()))

Out: [' ', "'", '-', '?', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J','K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']

Alles ist richtig. Infolgedessen betrafen die Korrekturen 1.253.882 oder 89% der Datensätze. Die Anzahl der eindeutigen Namen verringerte sich von 660.207 auf 599.186, dh um 61.000 oder fast 10%. Beeindruckend! Speichern Sie in einer neuen Datei, nachdem Sie die Vereinigung der ar-Datensätze wieder in das rd- Protokollwörterbuch übersetzt haben .

pkl.dump(rd, open(r'D:\tri\details4.pkl', 'wb'))

Jetzt müssen wir die Ordnung wiederherstellen. Das heißt, dass alle Datensätze wie folgt aussehen würden: Vorname Nachname oder Nachname Vorname . Welches ist zu bestimmen. Zwar enthalten einige Protokolle neben Vor- und Nachnamen auch Zweitnamen. Und es kann vorkommen, dass dieselbe Person in verschiedenen Protokollen unterschiedlich geschrieben ist - irgendwo mit einem zweiten Vornamen, irgendwo ohne. Dies beeinträchtigt seine Identifizierung. Versuchen Sie daher, den zweiten Vornamen zu entfernen. Patronymie für Männer hat normalerweise die Endung "hiv" und für Frauen "vna" . Es gibt jedoch Ausnahmen. Zum Beispiel - Ilyich, Ilyinichna, Nikitich, Nikitichna. Es stimmt, es gibt nur sehr wenige solcher Ausnahmen. Wie bereits erwähnt, kann das Format von Namen innerhalb eines Protokolls als permanent angesehen werden. Um Patronymie loszuwerden, müssen Sie daher die Rasse finden, in der sie vorhanden sind. Dazu finden die Gesamtzahl der Fragmente „vich“ und „vna“ in der Spalte Nameund vergleichen Sie sie mit der Gesamtzahl der Einträge in jedem Protokoll. Wenn diese Zahlen nahe beieinander liegen, gibt es einen zweiten Vornamen, andernfalls nicht. Es ist unvernünftig, nach einem strengen Spiel zu suchen, weil Selbst bei Rennen, bei denen beispielsweise Zweitnamen aufgezeichnet werden, können Ausländer teilnehmen, und sie werden ohne ihn aufgezeichnet. Es kommt auch vor, dass der Teilnehmer seinen zweiten Vornamen vergessen hat oder nicht angeben wollte. Andererseits gibt es auch Nachnamen, die auf "vich" enden, viele davon in Belarus und anderen Ländern mit den Sprachen der slawischen Gruppe. Außerdem haben wir Transliteration durchgeführt. Es war möglich, diese Analyse vor der Transliteration durchzuführen, aber dann besteht die Möglichkeit, dass ein Protokoll übersehen wird, in dem es Vornamen gibt, das jedoch zunächst bereits in lateinischer Sprache vorliegt. Also ist alles in Ordnung.

Wir werden also nach allen Protokollen suchen, in denen die Anzahl der Fragmente "vich" und "vna" in der Spalte angegeben istDer Name macht mehr als 50% der Gesamtzahl der Einträge im Protokoll aus.

wp = {} #wp – with patronymic 

for e in rd:
    nvich = (''.join(rd[e]['name'])).count('vich') 
    nvna = (''.join(rd[e]['name'])).count('vna')
    if nvich + nvna > 0.5*len(rd[e]):
        wp[e] = rd[e]

Es gibt 29 solcher Protokolle. Eines davon ist: Und es ist interessant, dass wenn sich anstelle von 50% 20% oder umgekehrt 70% nehmen, sich das Ergebnis nicht ändert, es das gleiche 29 gibt. Also haben wir die richtige Wahl getroffen. Dementsprechend weniger als 20% - die Auswirkung von Nachnamen, mehr als 70% - die Auswirkung einzelner Datensätze ohne Zweitnamen. Nachdem das Land mit Hilfe eines Pivot-Tisches überprüft worden war, stellte sich heraus, dass 25 von ihnen in Russland und 4 in Abchasien waren. Weitermachen. Wir werden nur Datensätze mit drei Komponenten verarbeiten, dh solchen, bei denen (vermutlich) ein Nachname, ein Name oder ein zweiter Vorname vorhanden ist.
placesexnamecountry...name raw
01MYaroslav Stanislavovich PavlishchevRUS...
12MVladimir Vasilevich PerezhiginRUS...
23MVladislav Evgenevich LitvinchukRUS...
34MSergey Gennadevich GavrilenkoRUS...
45MIvan Markovich MarkinRUS...
56MNikolay Evgenevich SokolovRUS...Nikolay Evgenevich Sokolov
67MAram Pavlovich KukhtievRUS...
78MAndrey Anatolevich AndreevRUS...
89MDenis Valerevich BulgakovRUS...
910MAleksandr Ivanovich KutsRUS...




sum_n3w = 0 # sum name of 3 words
sum_nnot3w = 0 # sum name not of 3 words

for e in wp: 
    sum_n3w += len([n for n in wp[e]['name'] if len(n.split()) == 3])
    sum_nnot3w += len(wp[e]) - n3w

Die Mehrheit dieser Aufzeichnungen liegt bei 86%. Nun diejenigen, in denen die drei Komponenten in die Spalten name0, name1, name2 unterteilt sind :

for e in wp:
    ind3 = [i for i in rd[e].index if len(rd[e].loc[i,'name'].split()) == 3]
    rd[e]['name0'] = ''
    rd[e]['name1'] = ''
    rd[e]['name2'] = ''
    rd[e].loc[ind3, 'name0'] = rd[e].loc[ind3,'name'].str.split().str[0]
    rd[e].loc[ind3, 'name1'] = rd[e].loc[ind3,'name'].str.split().str[1]
    rd[e].loc[ind3, 'name2'] = rd[e].loc[ind3,'name'].str.split().str[2]

So sieht eines der Protokolle aus: Insbesondere hier ist klar, dass die Aufzeichnung der beiden Komponenten nicht verarbeitet wurde. Jetzt müssen Sie für jedes Protokoll bestimmen, welche Spalte einen zweiten Vornamen hat. Es gibt nur zwei Optionen - Name1, Name2 , da dies nicht an erster Stelle stehen kann. Einmal festgelegt, werden wir einen neuen Namen bereits ohne ihn sammeln.
namename0name1name2...name raw
0Lekomtsev Denis NikolaevichLekomtsevDenisNikolaevich...
1Ivanov Andrey AleksandrovichIvanovAndreyAleksandrovich...
2Ivanov Evgeniy VasilevichIvanovEvgeniyVasilevich...
3Setepov Vladislav...
4Mishanin Sergey YurevichMishaninSergeyYurevich...
5Baranov Andrey AleksandrovichBaranovAndreyAleksandrovich...
6Nakaryakov Dmitriy ValerevichNakaryakovDmitriyValerevich...
7Tretyakov Dmitriy ValentinovichTretyakovDmitriyValentinovich...
8Kuznetsov Stanislav VladimirovichKuznetsovStanislavVladimirovich...
9Dubrovin Maksim SergeevichDubrovinMaksimSergeevich...
10Karpov Anatoliy SergeevichKarpovAnatoliySergeevich...


for e in wp:    
    n1=(''.join(rd[e]['name1'])).count('vich')+(''.join(rd[e]['name1'])).count('vna')
    n2=(''.join(rd[e]['name2'])).count('vich')+(''.join(rd[e]['name2'])).count('vna')

    if (n1 > n2):
        rd[e]['new name'] = rd[e]['name0'] + ' ' + rd[e]['name2']
    else:
        rd[e]['new name'] = rd[e]['name0'] + ' ' + rd[e]['name1']

namename0name1name2new namename raw
0Gorik Pavel VladimirovichGorikPavelVladimirovichGorik Pavel
1Korobov Oleg AnatolevichKorobovOlegAnatolevichKorobov Oleg
2Pavlishchev Yaroslav StanislavovichPavlishchevYaroslavStanislavovichPavlishchev Yaroslav
3Fedorov Nikolay NikolaevichFedorovNikolayNikolaevichFedorov Nikolay
4Medvedev Andrey AleksandrovichMedvedevAndreyAleksandrovichMedvedev Andrey
5Popov Sergey EduardovichPopovSergeyEduardovichPopov Sergey
6Dumchev Andrey ViktorovichDumchevAndreyViktorovichDumchev Andrey
7Trusov Mikhail VladimirovichTrusovMikhailVladimirovichTrusov Mikhail
8Demichev Yuriy AnatolevichDemichevYuriyAnatolevichDemichev Yuriy
9Pushkin Boris SergeevichPushkinBorisSergeevichPushkin Boris
10Lando Aleksandr BorisovichLandoAleksandrBorisovichLando Aleksandr
Nun vorsichtig den neuen Namen der Hauptspalte zuweisen Namen , wo er nicht leer ist, und löschen Sie die Hilfsspalten.

for e in wp:
    ind = rd[e][rd[e]['new name'].str.strip() != ''].index
    rd[e].loc[ind, 'name'] = rd[e].loc[ind, 'new name']
    rd[e] = rd[e].drop(columns = ['name0','name1','name2','new name'])

placesexnamecountry...name raw
01MYaroslav PavlishchevRUS...
12MVladimir PerezhiginRUS...
23MVladislav LitvinchukRUS...
34MSergey GavrilenkoRUS...
45MIvan MarkinRUS...
56MNikolay SokolovRUS...Nikolay Evgenevich Sokolov
67MAram KukhtievRUS...
78MAndrey AndreevRUS...
89MDenis BulgakovRUS...
910MAleksandr KutsRUS...
1011MAleksandr LandoRUS...
Das ist also alles. Wir haben 2.035 Einträge bearbeitet. Nicht schlecht. Gerettet.

pkl.dump(rd, open(r'D:\tri\details5.pkl', 'wb'))

Jetzt müssen Sie die Namen in die gleiche Reihenfolge bringen. Das heißt, es ist erforderlich, dass in allen Protokollen der Name zuerst gefolgt vom Nachnamen oder umgekehrt - zuerst der Nachname, dann der Vorname, auch in allen Protokollen. Kommt darauf an was mehr, jetzt werden wir es herausfinden. Die Situation wird etwas kompliziert durch die Tatsache, dass der vollständige Name aus mehr als zwei Wörtern bestehen kann, selbst nachdem wir den zweiten Vornamen entfernt haben.

ar['nwin'] = ar['name'].str.count(' ') + 1 # nwin – number of words in name
ar.loc[ar['name'] == '','nwin'] = 0
100*ar['nwin'].value_counts()/len(ar)

Anzahl der Wörter in einem Namen Anzahl der Datensätze Anteil der Datensätze (%) Natürlich besteht die überwiegende Mehrheit (91%) aus zwei Wörtern - nur einem Namen und einem Nachnamen. Es gibt aber auch sehr viele Einträge mit drei und vier Wörtern. Schauen wir uns die Nationalität solcher Aufzeichnungen an:
(%)
2128527090.74426
31022207.217066
4224201.582925
034540.243864
523850.168389
64690.033113
1800.005648
7570.004024
850.000353
1040.000282
910.000071


ar[ar['nwin'] >= 3]['country'].value_counts()[:12]

Out:
ESP 28435
MEX 10561
USA 7608
DNK 7178
BRA 6321
NLD 5748
DEU 4310
PHL 3941
ZAF 3862
ITA 3691
BEL 3596
FRA 3323

Nun, an erster Stelle steht Spanien, an zweiter Stelle - Mexiko, ein hispanisches Land, weiter als die Vereinigten Staaten, wo es auch historisch viele Hispanics gibt. Brasilien und die Philippinen sind ebenfalls spanische (und portugiesische) Namen. Dänemark, die Niederlande, Deutschland, Südafrika, Italien, Belgien und Frankreich sind eine andere Sache. Manchmal kommt dem Nachnamen einfach eine Art Präfix vor, daher gibt es mehr als zwei Wörter. In all diesen Fällen besteht der Name jedoch normalerweise aus einem Wort und der Nachname aus zwei, drei. Natürlich gibt es Ausnahmen von dieser Regel, aber wir werden sie nicht mehr verarbeiten. Zunächst müssen Sie für jedes Protokoll bestimmen, welche Art von Reihenfolge es gibt: Name-Nachname oder umgekehrt. Wie kann man das machen? Die folgende Idee kam mir: Erstens ist die Vielfalt der Nachnamen normalerweise viel größer als die Vielfalt der Namen. Dies sollte auch im Rahmen eines Protokolls der Fall sein. Zweitens,Die Länge des Namens ist normalerweise kleiner als die Länge des Nachnamens (auch bei nicht zusammengesetzten Nachnamen). Wir werden eine Kombination dieser Kriterien verwenden, um die vorläufige Bestellung zu bestimmen.

Wählen Sie das erste und das letzte Wort im vollständigen Namen aus:

ar['new name'] = ar['name']
ind = ar[ar['nwin'] < 2].index
ar.loc[ind, 'new name'] = '. .' #  ,   str.split()   
ar['wfin'] = ar['new name'].str.split().str[0] #fwin  – first word in name
ar['lwin'] = ar['new name'].str.split().str[-1]#lfin – last word in name

Konvertieren Sie den kombinierten ar- Datenrahmen zurück in das rd- Wörterbuch, sodass die neuen Spalten nwin, ns0, ns in den Datenrahmen jedes Rennens fallen. Als nächstes bestimmen wir die Anzahl der Protokolle mit der Reihenfolge „Vorname Nachname“ und die Anzahl der Protokolle mit der umgekehrten Reihenfolge gemäß unserem Kriterium. Wir werden nur Einträge betrachten, bei denen der vollständige Name aus zwei Wörtern besteht. Speichern Sie gleichzeitig den Namen (Vornamen) in einer neuen Spalte:

name_surname = {}
surname_name = {}

for e in rd:
    d = rd[e][rd[e]['nwin'] == 2]

    if len(d['fwin'].unique()) < len(d['lwin'].unique()) and len(''.join(d['fwin'])) < len(''.join(d['lwin'])):
        name_surname[e] = d
        rd[e]['first name'] = rd[e]['fwin']

    if len(d['fwin'].unique()) > len(d['lwin'].unique()) and len(''.join(d['fwin'])) > len(''.join(d['lwin'])):
        surname_name[e] = d
        rd[e]['first name'] = rd[e]['lwin']

Es stellte sich Folgendes heraus: die Reihenfolge Vorname Nachname - 244 Protokolle, die Reihenfolge Nachname Vorname - 1.508 Protokolle.

Dementsprechend werden wir zu dem Format führen, das üblicher ist. Es stellte sich heraus, dass die Summe geringer war als der Gesamtbetrag, da wir gleichzeitig und mit strikter Ungleichheit die Erfüllung von zwei Kriterien überprüft haben. Es gibt Protokolle, in denen nur eines der Kriterien erfüllt ist oder vielleicht, aber es ist unwahrscheinlich, dass Gleichheit auftritt. Dies ist jedoch völlig unwichtig, da das Format definiert ist.

Unter der Annahme, dass wir die Reihenfolge mit ausreichend hoher Genauigkeit bestimmt haben, ohne zu vergessen, dass sie nicht 100% genau ist, werden wir diese Informationen verwenden. Suchen Sie die beliebtesten Namen in der Spalte mit den Vornamen :

vc = ar['first name'].value_counts()

Nehmen Sie diejenigen, die sich mehr als hundert Mal getroffen haben:

pfn=vc[vc>100] #pfn – popular first names

Es gab 1.673 von ihnen. Hier sind die ersten hundert von ihnen, in absteigender Reihenfolge der Beliebtheit angeordnet: Anhand dieser Liste werden wir nun alle Protokolle durchgehen und vergleichen, wo es mehr Übereinstimmungen gibt - im ersten Wort des Namens oder im letzten. Wir werden nur Namen mit zwei Wörtern betrachten. Wenn es mehr Übereinstimmungen mit dem letzten Wort gibt, ist die Reihenfolge korrekt, wenn mit dem ersten das Gegenteil der Fall ist. Und hier sind wir sicherer, das heißt, Sie können dieses Wissen nutzen, und wir werden mit jedem Durchgang eine Liste der Namen ihres nächsten Protokolls zur anfänglichen Liste der populären Namen hinzufügen. Wir sortieren die Protokolle nach der Häufigkeit des Auftretens von Namen aus der Anfangsliste vor, um zufällige Fehler zu vermeiden, und erstellen eine umfangreichere Liste für die Protokolle, bei denen es nur wenige Übereinstimmungen gibt und die gegen Ende des Zyklus verarbeitet werden.
['Michael', 'David', 'Thomas', 'John', 'Daniel', 'Mark', 'Peter', 'Paul', 'Christian', 'Robert', 'Martin', 'James', 'Andrew', 'Chris', 'Richard', 'Andreas', 'Matthew', 'Brian', 'Patrick', 'Scott', 'Kevin', 'Stefan', 'Jason', 'Eric', 'Christopher', 'Alexander', 'Simon', 'Mike', 'Tim', 'Frank', 'Stephen', 'Steve', 'Andrea', 'Jonathan', 'Markus', 'Marco', 'Adam', 'Ryan', 'Jan', 'Tom', 'Marc', 'Carlos', 'Jennifer', 'Matt', 'Steven', 'Jeff', 'Sergey', 'William', 'Aleksandr', 'Sarah', 'Alex', 'Jose', 'Andrey', 'Benjamin', 'Sebastian', 'Ian', 'Anthony', 'Ben', 'Oliver', 'Antonio', 'Ivan', 'Sean', 'Manuel', 'Matthias', 'Nicolas', 'Dan', 'Craig', 'Dmitriy', 'Laura', 'Luis', 'Lisa', 'Kim', 'Anna', 'Nick', 'Rob', 'Maria', 'Greg', 'Aleksey', 'Javier', 'Michelle', 'Andre', 'Mario', 'Joseph', 'Christoph', 'Justin', 'Jim', 'Gary', 'Erik', 'Andy', 'Joe', 'Alberto', 'Roberto', 'Jens', 'Tobias', 'Lee', 'Nicholas', 'Dave', 'Tony', 'Olivier', 'Philippe']



sbpn = pd.DataFrame(columns = ['event', 'num pop names'], index=range(len(rd))) # sbpn - sorted by popular names

for i in range(len(rd)):
    e = list(rd.keys())[i]
    sbpn.loc[i, 'event'] = e
    sbpn.loc[i, 'num pop names'] = len(set(pfn).intersection(rd[e]['first name']))

sbnp=sbnp.sort_values(by = 'num pop names',ascending=False)
sbnp = sbnp.reset_index(drop=True) 

eventnum pop names
0Ironman World Championship 70.3 2016811
1Ironman World Championship 2019781
2Ironman World Championship 70.3 2015778
3Ironman Mallorca 70.3 2014776
4Ironman World Championship 2018766
5Challenge Roth Long 2019759
.........
1917Challenge Gran Canaria Olympic 20190
1918Challenge Gran Canaria Middle 20170
1919Challenge Forte Village-Sardinia Sprint 20170
1920ITU European Cup Kuopio Sprint 20070
1921ITU World Cup Madeira Olympic 20020


tofix = []

for i in range(len(rd)):
    e = sbpn.loc[i, 'event']

    if len(set(list(rd[e]['fwin'])).intersection(pfn)) > len(set(list(rd[e]['lwin'])).intersection(pfn)):
        tofix.append(e)
        pfn = list(set(pfn + list(rd[e]['fwin'])))
    else:
        pfn = list(set(pfn + list(rd[e]['lwin'])))

Es gab 235 Protokolle. Das ist ungefähr das Gleiche wie in erster Näherung (244). Um sicherzugehen, habe ich mir die ersten drei Datensätze von jedem selektiv angesehen und sichergestellt, dass alles korrekt war. Prüfen Sie auch , dass die erste Stufe der Sortier gab 36 falsche Einträge aus der Klasse Namen Nachnamen und 2 falsch aus Class Namen Namen . Ich habe mir die ersten drei Platten von jeder angesehen, tatsächlich hat die zweite Stufe perfekt funktioniert. Nun müssen tatsächlich die Protokolle repariert werden, bei denen die falsche Reihenfolge gefunden wird:

for e in tofix:
    ind = rd[e][rd[e]['nwin'] > 1].index
    rd[e].loc[ind,'name'] = rd[e].loc[ind,'name'].str.split(n=1).str[1] + ' ' +                   rd[e].loc[ind,'name'].str.split(n=1).str[0]

Hier in der Aufteilung haben wir die Anzahl der Teile mit dem Parameter n begrenzt . Die Logik lautet wie folgt: Ein Name ist ein Wort, das erste in einem vollständigen Namen. Alles andere ist ein Nachname (kann aus mehreren Wörtern bestehen). Tauschen Sie sie einfach aus.

Jetzt werden unnötige Spalten entfernt und gespeichert:

for e in rd:
    rd[e] = rd[e].drop(columns = ['new name', 'first name', 'fwin','lwin', 'nwin'])

pkl.dump(rd, open(r'D:\tri\details6.pkl', 'wb'))

Überprüfen Sie das Ergebnis. Ein zufälliges Dutzend fester Datensätze: Insgesamt wurden 108.000 Datensätze repariert. Die Anzahl der eindeutigen vollständigen Namen verringerte sich von 598 auf 547 Tausend. Fein! Mit der Formatierung fertig.
placesexnamecountrygroup...name raw
188189MAzhel DmitriyBLR...
9697MBostina CristianROU...Cristian Bostina
17571758MLowe JonathanAUSM30-34...Jonathan LOWE
599600MBaerwald ManuelDEU...Manuel BAERWALD
657658MKrumdieck RalfDEU...Ralf KRUMDIECK
354355FKnapp SamanthaUSAF30-34...Samantha Knapp
375376MRintalaulaja MikaFINM40-44...Mika Rintalaulaja
13041305MDee JimUSAM50-54...Jim DEE
178179MHalibert GregFRA...GREG HALIBERT
27402741FComia MarissaUSAF45-49...Marissa COMIA


Teil 3. Wiederherstellung unvollständiger Daten


Fahren Sie nun mit der Wiederherstellung der fehlenden Daten fort. Und es gibt solche.

Land


Beginnen wir mit dem Land. Finden Sie alle Datensätze, in denen das Land nicht angegeben ist:

arnc = ar[ar['country'] == ''] #arnc – all records with no country

Es gibt 3.221 von ihnen. Hier sind 10 zufällige:
eventplacesexnamecountrygroup...country raw
...1633MGuerrero Pla AngelM30-34...E
...258MBellm MathiasM35-39...D
...655MMoratto AlessioM40-44...I
...1317MSolari Jean-JacquesM50-54...TAH
...1311FDuranel IsabelleF40-44...F
...1012MEndler MaximilianM40-44...D
...284MSchreiner JorgM40-44...D
...14MButturini Jacopo...ITU
...204MLindner ThomasM40-44...D
...1168MGramke PeterM45-49...D


nnc = arnc['name'].unique() #nnc - names with no country

Die Anzahl der eindeutigen Namen unter Datensätzen ohne Land beträgt 3 051. Mal sehen, ob diese Anzahl reduziert werden kann.

Tatsache ist, dass sich die Leute beim Triathlon selten auf nur ein Rennen beschränken, sondern in der Regel regelmäßig, mehrmals pro Saison, von Jahr zu Jahr an Wettbewerben teilnehmen und ständig trainieren. Daher gibt es für viele Namen in den Daten höchstwahrscheinlich mehr als einen Datensatz. Versuchen Sie zum Wiederherstellen von Informationen über das Land, Datensätze mit demselben Namen unter den Datensätzen zu finden, in denen das Land angegeben ist.

arwc = ar[ar['country'] != ''] #arwc – all records with country
nwc = arwc['name'].unique() #nwc – names with country
tofix = set(nnc).intersection(nwc)

Out: ['Kleber-Schad Ute Cathrin', 'Sellner Peter', 'Pfeiffer Christian', 'Scholl Thomas', 'Petersohn Sandra', 'Marchand Kurt', 'Janneck Britta', 'Angheben Riccardo', 'Thiele Yvonne', 'Kie?Wetter Martin', 'Schymik Gerhard', 'Clark Donald', 'Berod Brigitte', 'Theile Markus', 'Giuliattini Burbui Margherita', 'Wehrum Alexander', 'Kenny Oisin', 'Schwieger Peter', 'Grosse Bianca', 'Schafter Carsten', 'Breck Dirk', 'Mautes Christoph', 'Herrmann Andreas', 'Gilbert Kai', 'Steger Peter', 'Jirouskova Jana', 'Jehrke Michael', 'Valentine David', 'Reis Michael', 'Wanka Michael', 'Schomburg Jonas', 'Giehl Caprice', 'Zinser Carsten', 'Schumann Marcus', 'Magoni Livio', 'Lauden Yann', 'Mayer Dieter', 'Krisa Stefan', 'Haberecht Bernd', 'Schneider Achim', 'Gibanel Curto Antonio', 'Miranda Antonio', 'Juarez Pedro', 'Prelle Gerrit', 'Wuste Kay', 'Bullock Graeme', 'Hahner Martin', 'Kahl Maik', 'Schubnell Frank', 'Hastenteufel Marco', …]

Es gab 2.236 von ihnen, das sind fast drei Viertel. Jetzt müssen Sie für jeden Namen aus dieser Liste das Land anhand der Datensätze bestimmen, in denen es sich befindet. Es kommt jedoch vor, dass derselbe Name in mehreren Datensätzen und in verschiedenen Ländern vorkommt. Dies ist entweder der Namensvetter oder vielleicht die Person, die sich bewegt hat. Deshalb verarbeiten wir zuerst diejenigen, bei denen alles einzigartig ist.

fix = {}

for n in tofix:
    nr = arwc[arwc['name'] == n] 
	
    if len(nr['country'].unique()) == 1:
        fix[n] = nr['country'].iloc[0]

In einer Schleife gemacht. Aber ehrlich gesagt klappt es schon lange - ungefähr drei Minuten. Wenn es eine Größenordnung mehr Einträge gäbe, müssten Sie wahrscheinlich eine Vektorimplementierung entwickeln. Es gab 2.013 Einträge oder 90% des Potenzials.

Namen, für die verschiedene Länder in unterschiedlichen Datensätzen vorkommen können, beziehen sich auf das Land, das am häufigsten vorkommt.

if n not in fix:
    nr = arwc[arwc['name'] == n]
    vc = nr['country'].value_counts()    
	
    if vc[0] > vc[1]:
        fix[n] = vc.index[0]

So wurden Übereinstimmungen für 2.208 Namen oder 99% aller potenziellen Namen gefunden. Wir wenden diese Entsprechungen an:
{'Kleber-Schad Ute Cathrin': 'DEU', 'Sellner Peter': 'AUT', 'Pfeiffer Christian': 'AUT', 'Scholl Thomas': 'DEU', 'Petersohn Sandra': 'DEU', 'Marchand Kurt': 'BEL', 'Janneck Britta': 'DEU', 'Angheben Riccardo': 'ITA', 'Thiele Yvonne': 'DEU', 'Kie?Wetter Martin': 'DEU', 'Clark Donald': 'GBR', 'Berod Brigitte': 'FRA', 'Theile Markus': 'DEU', 'Giuliattini Burbui Margherita': 'ITA', 'Wehrum Alexander': 'DEU', 'Kenny Oisin': 'IRL', 'Schwieger Peter': 'DEU', 'Schafter Carsten': 'DEU', 'Breck Dirk': 'DEU', 'Mautes Christoph': 'DEU', 'Herrmann Andreas': 'DEU', 'Gilbert Kai': 'DEU', 'Steger Peter': 'AUT', 'Jirouskova Jana': 'CZE', 'Jehrke Michael': 'DEU', 'Wanka Michael': 'DEU', 'Giehl Caprice': 'DEU', 'Zinser Carsten': 'DEU', 'Schumann Marcus': 'DEU', 'Magoni Livio': 'ITA', 'Lauden Yann': 'FRA', 'Mayer Dieter': 'DEU', 'Krisa Stefan': 'DEU', 'Haberecht Bernd': 'DEU', 'Schneider Achim': 'DEU', 'Gibanel Curto Antonio': 'ESP', 'Juarez Pedro': 'ESP', 'Prelle Gerrit': 'DEU', 'Wuste Kay': 'DEU', 'Bullock Graeme': 'GBR', 'Hahner Martin': 'DEU', 'Kahl Maik': 'DEU', 'Schubnell Frank': 'DEU', 'Hastenteufel Marco': 'DEU', 'Tedde Roberto': 'ITA', 'Minervini Domenico': 'ITA', 'Respondek Markus': 'DEU', 'Kramer Arne': 'DEU', 'Schreck Alex': 'DEU', 'Bichler Matthias': 'DEU', …}



for n in fix:
    ind = arnc[arnc['name'] == n].index
    ar.loc[ind, 'country'] = fix[n]

eventplacesexnamecountrygroup...country raw
...1633MGuerrero Pla AngelESPM30-34...E
...258MBellm MathiasDEUM35-39...D
...655MMoratto AlessioITAM40-44...I
...1317MSolari Jean-JacquesPYFM50-54...TAH
...1311FDuranel IsabelleFRAF40-44...F
...1012MEndler MaximilianDEUM40-44...D
...284MSchreiner JorgDEUM40-44...D
...14MButturini JacopoHRV...ITU
...204MLindner ThomasDEUM40-44...D
...1168MGramke PeterDEUM45-49...D
Nach unseren Korrekturen verringerte sich die Anzahl der Datensätze ohne Land auf 909, dh mehr als dreimal. Obwohl die Gesamtzahl von 2.208 vor dem Hintergrund von eineinhalb Millionen nicht so groß ist, ist es immer noch schön.

Außerdem übersetzen wir wie üblich den kombinierten Datenrahmen wieder in das dritte Wörterbuch und speichern ihn.

pkl.dump(rd, open(r'D:\tri\details7.pkl', 'wb'))

Fußboden


Wie in den Ländern gibt es Aufzeichnungen, in denen das Geschlecht des Teilnehmers nicht angegeben ist.

ar[ar['sex'] == '']

Es gibt 2.538 von ihnen. Relativ wenige, aber wir werden wieder versuchen, noch weniger zu machen. Speichern Sie die ursprünglichen Werte in einer neuen Spalte.

ar['sex raw'] =ar['sex']

Im Gegensatz zu Ländern, in denen wir Informationen namentlich aus anderen Protokollen abgerufen haben, ist hier alles etwas komplizierter. Tatsache ist, dass die Daten voller Fehler sind und es viele Namen (insgesamt 2 101) gibt, die mit Markierungen beider Geschlechter gefunden werden.

arws = ar[(ar['sex'] != '')&(ar['name'] != '')] #arws – all records with sex
snds = arws[arws.duplicated(subset='name',keep=False)]#snds–single name different sex
snds = snds.drop_duplicates(subset=['name','sex'], keep = 'first')
snds = snds.sort_values(by='name')
snds = snds[snds.duplicated(subset = 'name', keep=False)]

snds

eventplacesexnamecountrygroup...country rawgroup rawsex raw
...428FAagaard IdaNORF40-44...NORF40-44F
...718MAagaard IdaNORM40-44...NORM40-44M
740MAarekol Tove AaseNORM50-54...NORM50-54M
...520FAarekol Tove AaseNORF50-54...NORF50-54F
...665FAaroy TorunnNORF40-44...NORF40-44F
...1591MAaroy TorunnNORM40-44...NORM40-44M
...70MAberg Cobo DoloresARGFPRO...ARGFPROM
...1258FAberg Cobo DoloresARGF30-34...ARGF30-34F
...1909FAboulfaida ZinebMARF35-39...MARF35-39F
...340MAboulfaida ZinebMARM35-39...MARM35-39M
...63FAbram FelicityAUSFPRO...AUSFPROF
...38MAbram FelicityAUSFJUNIOR...AUSFJUNIORM
...134MAbramowski JannickeDEUFPRO...GERFPROM
...323FAbramowski JannickeDEUF25-29...GERF25-29F
...21MAbrosimova AnastasiaRUSFPRO...RUSFPROM
...177FAbrosimova AnastasiaRUSFPRO...RUSFPROF
...188MAbysova IrinaRUSFPRO...RUSFPROM
...60FAbysova IrinaRUSFPRO...RUSFPROF
...312MAcaron FabiolaPRIFJUNIOR...PURFJUNIORM
...294FAcaron FabiolaPRIF45-49...PURF45-49F
...1500MAchampong BenjaminGBRM35-39...GBRM35-39M
...749FAchampong BenjaminGBRM35-39...GBRM35-39F
Ja, im Prinzip gibt es unisex (oder androgyne) Namen, dh solche, mit denen sowohl Jungen als auch Mädchen benannt werden. Und für asiatische Sportler ist es im Allgemeinen schwierig, das Geschlecht anhand des Namens zu bestimmen - vielleicht habe ich einfach nicht genug Wissen. Es ist jedoch kaum zu glauben, dass der Name Irina oder Anastasia einem Mann gehörte und Benjamin eine Frau genannt wurde. Darüber hinaus habe ich irgendwann festgestellt, dass es eine große Anzahl von Protokollen gibt, in denen alle Teilnehmer mit einem Geschlecht gekennzeichnet sind.

rss = [rd[e] for e in rd if len(rd[e][rd[e]['sex'] != '']['sex'].unique()) == 1] #rss – races with single sex

Es gibt 633 von ihnen. Es scheint, dass dies durchaus möglich ist, nur ein Protokoll separat für Frauen, separat für Männer. Tatsache ist jedoch, dass fast alle diese Protokolle Altersgruppen beider Geschlechter enthalten (männliche Altersgruppen beginnen mit dem Buchstaben M , weiblich - mit dem Buchstaben F ). Beispiel: Der Name der Altersgruppe beginnt voraussichtlich mit dem Buchstaben M für Männer und dem Buchstaben F für Frauen. In den beiden vorherigen Beispielen trotz der Fehler in der Spalte Geschlecht

'ITU World Cup Tiszaujvaros Olympic 2002'
placesexnamecountrygroup...country rawgroup rawname raw
76MDederko EwaPOLFPRO...POLFPRODederko Ewa
84MChenevier GiuniaITAFPRO...ITAFPROChenevier Giunia
36MO'Grady GrahamNZLMPRO...NZLMPROO'Grady Graham
23MDanek MichalCZEMPRO...CZEMPRODanek Michal
74MPeon CaroleFRAFPRO...FRAFPROPeon Carole
48MHechenblaickner DanielAUTMPRO...AUTMPROHechenblaickner Daniel
70MBlatchford LizGBRFPRO...GBRFPROBlatchford Liz
1MWalton CraigAUSMPRO...AUSMPROWalton Craig
20MHobor PeterHUNMPRO...HUNMPROHobor Peter
56MKaldau SzabolcsHUNMPRO...HUNMPROKaldau Szabolcs
Der Name der Gruppe schien das Geschlecht des Mitglieds immer noch richtig zu beschreiben. Anhand mehrerer Beispielbeispiele gehen wir davon aus, dass die Gruppe korrekt angegeben ist und das Geschlecht möglicherweise falsch angegeben ist. Suchen Sie alle Einträge, bei denen der erste Buchstabe im Gruppennamen nicht mit dem Geschlecht übereinstimmt. Wir werden den ursprünglichen Namen der Gruppengruppe roh nehmen , da während der Standardisierung viele Datensätze ohne Gruppe belassen wurden, aber jetzt nur noch den ersten Buchstaben benötigen, sodass der Standard nicht wichtig ist.

ar['grflc'] = ar['group raw'].str.upper().str[0] #grflc – group raw first letter capital
grncs = ar[(ar['grflc'].isin(['M','F']))&(ar['sex']!=ar['grflc'])] #grncs – group raw not consistent with sex

Es gibt 26 161 solcher Aufzeichnungen. Korrigieren wir das Geschlecht entsprechend dem Namen der Altersgruppe:

ar.loc[grncs.index, 'sex'] = grncs['grflc']

Schauen wir uns das Ergebnis an: Gut. Wie viele Datensätze sind jetzt ohne Geschlecht?
eventplacesexnamecountrygroup...country rawgroup rawsex rawgrflc
...59FUeda AiJPNFPRO...JPNFPROMF
...50FZemanova LenkaCZEFPRO...CZEFPROMF
...83FSpearing KyleighUSAFPRO...USAFPROMF
...63FAbysova IrinaRUSFPRO...RUSFPROMF
...57FKnapp AnjaDEUFPRO...GERFPROMF
...68MMatthews AndrewGBRM30-34...GBRM30-34FM
...46FRappaport SummerUSAFPRO...USAFPROMF
...60FReid AileenIRLFPRO...IRLFPROMF
...142FMcdowall EdwinaGBRF45-49...GBRF45-49F
...141MO'Bray LukeGBRM30-34...GBRM30-34M


ar[(ar['sex'] == '')&(ar['name'] != '')]

Es stellt sich genau eins heraus! Nun, die Gruppe ist nicht wirklich angegeben, aber anscheinend ist dies eine Frau. Emily ist ein weiblicher Name, außerdem ist diese Teilnehmerin (oder ihr Namensvetter) ein Jahr zuvor fertig geworden, und in diesem Protokoll sind Geschlecht und Gruppe angegeben. Hier manuell wiederherstellen * und weitermachen.
eventplacesexnamecountrygroup...country rawgroup rawsex rawgrflc
London Triathlon Olympic 2019672Stather EmilyGBR...GBRunknownU


eventplacesexnamecountrygroup...country rawgroup rawsex rawgrflc
Ironman Staffordshire 70.3 20181859FStather EmilyGBRF40-44...GBRF40-44FF



ar.loc[arns.index, 'sex'] = 'F'

Jetzt sind alle Aufzeichnungen nach Geschlecht.

* Im Allgemeinen ist es natürlich falsch, dies zu tun. Wenn sich bei wiederholten Durchläufen etwas in der Kette ändert, z. B. bei der Namenskonvertierung, gibt es möglicherweise mehr als einen Datensatz ohne Geschlecht, und nicht alle sind weiblich. Es tritt ein Fehler auf. Daher müssen Sie entweder eine umfangreiche Logik einfügen, um in anderen Protokollen nach einem Teilnehmer mit demselben Namen und Geschlecht zu suchen, z. B. um ein Land wiederherzustellen, und um es zu testen, oder um diese Logik nicht unnötig zu komplizieren, eine Überprüfung hinzuzufügen, dass nur ein Datensatz gefunden wird und der Name ist so und so, andernfalls wird eine Ausnahme ausgelöst, die den gesamten Laptop stoppt. Sie können eine Abweichung vom Plan feststellen und eingreifen.

if len(arns) == 1 and arns['name'].iloc[0] == 'Stather Emily':
    ar.loc[arns.index, 'sex'] = 'F'
else:
    raise Exception('Different scenario!')

Es scheint, dass sich dies beruhigen kann. Tatsache ist jedoch, dass Korrekturen auf der Annahme beruhen, dass die Gruppe korrekt angegeben ist. Und tatsächlich ist es so. Fast immer. Fast. Trotzdem wurden versehentlich mehrere Inkonsistenzen festgestellt. Lassen Sie uns nun versuchen, alle zu ermitteln, gut oder so weit wie möglich. Wie bereits erwähnt, war es im ersten Beispiel genau die Tatsache, dass das Geschlecht aufgrund seiner eigenen Vorstellungen über männliche und weibliche Namen, die uns bewachten, nicht dem Namen entsprach.

Finden Sie alle Namen in den männlichen und weiblichen Aufzeichnungen. Hier wird der Name als Name verstanden und nicht als vollständiger Name, dh ohne Nachnamen, was auf Englisch als Vorname bezeichnet wird .

ar['fn'] = ar['name'].str.split().str[-1] #fn – first name
mfn = list(ar[ar['sex'] == 'M']['fn'].unique()) #mfn – male first names

Insgesamt sind 32.508 männliche Namen aufgeführt. Hier sind die 50 beliebtesten:
['Michael', 'David', 'Thomas', 'John', 'Daniel', 'Mark', 'Peter', 'Paul', 'Christian', 'Robert', 'Martin', 'James', 'Andrew', 'Chris', 'Richard', 'Andreas', 'Matthew', 'Brian', 'Kevin', 'Patrick', 'Scott', 'Stefan', 'Jason', 'Eric', 'Alexander', 'Christopher', 'Simon', 'Mike', 'Tim', 'Frank', 'Stephen', 'Steve', 'Jonathan', 'Marco', 'Markus', 'Adam', 'Ryan', 'Tom', 'Jan', 'Marc', 'Carlos', 'Matt', 'Steven', 'Jeff', 'Sergey', 'William', 'Aleksandr', 'Andrey', 'Benjamin', 'Jose']


ffn = list(ar[ar['sex'] == 'F']['fn'].unique()) #ffn – female first names

Weniger Frauen - 14 423. Am beliebtesten: Gut, es scheint logisch auszusehen. Mal sehen, ob es Kreuzungen gibt.
['Jennifer', 'Sarah', 'Laura', 'Lisa', 'Anna', 'Michelle', 'Maria', 'Andrea', 'Nicole', 'Jessica', 'Julie', 'Elizabeth', 'Stephanie', 'Karen', 'Christine', 'Amy', 'Rebecca', 'Susan', 'Rachel', 'Anne', 'Heather', 'Kelly', 'Barbara', 'Claudia', 'Amanda', 'Sandra', 'Julia', 'Lauren', 'Melissa', 'Emma', 'Sara', 'Katie', 'Melanie', 'Kim', 'Caroline', 'Erin', 'Kate', 'Linda', 'Mary', 'Alexandra', 'Christina', 'Emily', 'Angela', 'Catherine', 'Claire', 'Elena', 'Patricia', 'Charlotte', 'Megan', 'Daniela']



mffn = set(mfn).intersection(ffn) #mffn – male-female first names

Es gibt. Und es gibt 2.811 von ihnen. Schauen wir sie uns genauer an. Zunächst finden wir heraus, wie viele Datensätze diese Namen haben:

armfn = ar[ar['fn'].isin(mffn)] #armfn – all records with male-female names

Es gibt 725 562. Das ist die Hälfte! Das ist erstaunlich! Es gibt fast 37.000 eindeutige Namen, aber die Hälfte der Datensätze hat insgesamt 2.800. Mal sehen, welche Namen am beliebtesten sind. Erstellen Sie dazu einen neuen Datenrahmen, in dem diese Namen Indizes sind:

df = pd.DataFrame(armfn['fn'].value_counts())
df = df.rename(columns={'fn':'total'})

Wir berechnen, wie viele männliche und weibliche Datensätze mit jedem von ihnen.

df['M'] = armfn[armfn['sex'] == 'M']['fn'].value_counts()
df['F'] = armfn[armfn['sex'] == 'F']['fn'].value_counts()

totalMF
Michael206482063810
David18493184858
Thomas12746127406
John11634116322
Daniel11045110414
Mark10968109653
Peter10692106911
Paul961696142
Christian886388594
Robert866686642
............
Also ... es sieht verdächtig aus. Soweit ich weiß, sind alle diese Namen männlich. Aber mit jedem von ihnen gibt es eine kleine Anzahl weiblicher Aufzeichnungen. Dies sind wahrscheinlich Datenfehler.

Schauen wir uns die weiblichen Namen an.

df.sort_values(by = 'F', ascending=False)

totalMF
Jennifer365233649
Sarah328843284
Laura263632633
Lisa261822616
Anna2563102553
Michelle237312372
Maria25553862169
Andrea432322352088
Nicole202562019
Julie193821936
............
Das selbe. Fast. Heben Sie Andrea hervor , was in der Tat ein androgyner Name ist und aus irgendeinem Grund etwas weniger als Maria .

Vergessen Sie nicht, dass wir Daten von Menschen aus sehr unterschiedlichen Ländern auf der ganzen Welt recherchieren. In verschiedenen Kulturen kann der gleiche Name auf völlig unterschiedliche Weise verwendet werden. Hier ist ein Beispiel. Karen ist einer der beliebtesten weiblichen Namen aus unserer Liste, aber auf der anderen Seite gibt es den Namen Karen , der auf die gleiche Weise in Transliteration geschrieben wird, aber ausschließlich von Männern getragen wird. Glücklicherweise gibt es ein Paket, das all diese Weltweisheit enthält. Es heißt Gender-Guesser .

Es funktioniert so:

import gender_guesser.detector as gg

d = gg.Detector()
d.get_gender(u'Oleg')

Out: 'male'


d.get_gender(u'Evgeniya')

Out: 'female'

Alles ist in Ordnung. Aber wenn Sie den Namen Andrea überprüfen , dann gibt er auch weiblich aus , was nicht ganz richtig ist. Es stimmt, es gibt einen Ausweg. Wenn Sie sich die Eigenschaft names des Detektors ansehen , wird dort die gesamte Mehrdeutigkeit sichtbar.

d.names['Andrea']

Out: {'female': ' 4 4 3 4788 64 579 34 1 7 ',
'mostly_female': '5 6 7 ',
'male': ' 7 '}

Ja, das heißt, get_gender bietet Ihnen nur die wahrscheinlichste Option, aber in Wirklichkeit kann es viel komplizierter sein. Überprüfen Sie andere Namen:

d.names['Maria']

Out: {'female': '686 6 A 85986 A BA 3B98A75457 6 ',
'mostly_female': ' BBC A 678A9 '}


d.names['Oleg']

Out: {'male': ' 6 2 99894737 3 '}

Das heißt, die Liste der Namen für jeden Namen entspricht einem oder mehreren Schlüssel-Wert-Paaren, wobei der Schlüssel - es ist Geschlecht: männlich, WEIBLICH, meistens männlich, meistens weiblich, andy , und der Wert - die Liste der Werte des entsprechenden Landes: 1,2,3 ... .. 9ABC . Die Länder sind:

d.COUNTRIES

Out: ['great_britain', 'ireland', 'usa', 'italy', 'malta', 'portugal', 'spain', 'france', 'belgium', 'luxembourg', 'the_netherlands', 'east_frisia', 'germany', 'austria', 'swiss', 'iceland', 'denmark', 'norway', 'sweden', 'finland', 'estonia', 'latvia', 'lithuania', 'poland', 'czech_republic', 'slovakia', 'hungary', 'romania', 'bulgaria', 'bosniaand', 'croatia', 'kosovo', 'macedonia', 'montenegro', 'serbia', 'slovenia', 'albania', 'greece', 'russia', 'belarus', 'moldova', 'ukraine', 'armenia', 'azerbaijan', 'georgia', 'the_stans', 'turkey', 'arabia', 'israel', 'china', 'india', 'japan', 'korea', 'vietnam', 'other_countries']

Ich habe nicht ganz verstanden, was die alphanumerischen Bedeutungen oder ihre Abwesenheit in der Liste konkret bedeuten. Dies war jedoch nicht wichtig, da ich mich darauf beschränkte, nur die Namen zu verwenden, die eine eindeutige Interpretation haben. Das heißt, für die es nur ein Schlüssel-Wert-Paar gibt und der Schlüssel entweder männlich oder weiblich ist . Schreiben Sie für jeden Namen aus unserem Datenrahmen die Interpretation des Gender-Guesser :

df['sex from gg'] = ''

for n in df.index:
    if n in list(d.names.keys()):
        options = list(d.names[n].keys())
        if len(options) == 1 and options[0] == 'male':
            df.loc[n, 'sex from gg'] = 'M'
        if len(options) == 1 and options[0] == 'female':
            df.loc[n, 'sex from gg'] = 'F'

Es stellte sich heraus, 1.150 Namen. Hier sind die beliebtesten, die bereits oben besprochen wurden: Nun, nicht schlecht. Wenden Sie diese Logik nun auf alle Datensätze an.
totalMFsex from gg
Michael206482063810M
David18493184858M
Thomas12746127406M
John11634116322M
Daniel11045110414M
Mark10968109653M
Peter10692106911M
Paul961696142M
Christian886388594
Robert866686642M

totalMFsex from gg
Jennifer365233649F
Sarah328843284F
Laura263632633F
Lisa261822616F
Anna2563102553F
Michelle237312372F
Maria25553862169
Andrea432322352088
Nicole202562019F
Julie193821936


all_names = ar['fn'].unique()

male_names = []
female_names = []

for n in all_names:
    if n in list(d.names.keys()):
        options = list(d.names[n].keys())
        if len(options) == 1:
            if options[0] == 'male':
                male_names.append(n)
            if options[0] == 'female':
                female_names.append(n)

Gefunden 7 091 männliche Namen und 5 054 weibliche. Wenden Sie die Transformation an:

tofixm = ar[ar['fn'].isin(male_names)]
ar.loc[tofixm.index, 'sex'] = 'M'
tofixf = ar[ar['fn'].isin(female_names)]
ar.loc[tofixf.index, 'sex'] = 'F'

Wir schauen uns das Ergebnis an:

ar[ar['sex']!=ar['sex raw']]

30.352 Einträge korrigiert (zusammen mit der Korrektur durch den Namen der Gruppe). Wie üblich 10 zufällige: Nachdem wir sicher sind, dass wir das Geschlecht korrekt identifiziert haben, werden wir auch Standardgruppen in Einklang bringen. Mal sehen, wo sie nicht übereinstimmen:
eventplacesexnamecountrygroup...country rawgroup rawsex rawgrflc
...37FPilz ChristianeDEUFPRO...GERFPROMF
...92FBrault Sarah-AnneCANFPRO...CANFPROMF
...96FMurphy SusannaIRLFPRO...IRLFPROMF
...105FSpoelder RomyNLD...NEDFJUNIORMF
...424MWatson TomGBRM40-44...GBRM40-44FM
...81FMorel CharlotteFRA...FRAFJUNIORMF
...65FSelekhova OlgaRUS...RUSFU23MF
...166FKeat RebekahAUS...AUSFJUNIORMF
...119FEim NinaDEU...GERFQUAL…MF
...73FSukhoruchenkova EvgeniaRUSFPRO...RUSFPROMF


ar['gfl'] = ar['group'].str[0]
gncws = ar[(ar['sex'] != ar['gfl']) & (ar['group']!='')]

4.248 Einträge. Ersetzen Sie den ersten Buchstaben:

ar.loc[gncws.index, 'group'] = ar.loc[gncws.index, 'sex'] + ar.loc[gncws.index, 'group'].str[1:].index, 'sex']

eventplacesexnamecountrygroup...country rawgroup rawsex raw
...803FKenney JoelleUSAF35-39...USAM35-39M
...1432MHolmberg Henriette GormDNKM45-49...DENF45-49F
...503MTai Oy LeenMYSM40-44...MASF40-44F
...236FDissanayake ArunaLKAF25-29...SRIM25-29M
...1349FDelos Reyes Joshua RafaellePHLF18-24...PHIM18-24M
...543FVandekendelaere JaniqueBELF50-54...BELM50-54M
...1029MProvost ShaunUSAM25-29...USAF25-29F
...303FTorrens Vadell MaciaESPF30-34...ESPM30-34M
...1338FSuarez RenanBOLF35-39...BOLM35-39M
...502FEverlo LindaNLDF30-34...NEDM30-34M
Wahrscheinlich haben sich die Korrekturen irgendwo als falsch herausgestellt, aber jeder denkt früh, dass er mehr Gutes als Schaden angerichtet hat. Für die Statistik ist dies wichtig.

Das war's mit der Wiederherstellung des Sex. Wir löschen die Arbeitsspalten, übersetzen in das Wörterbuch und speichern.

pkl.dump(rd, open(r'D:\tri\details8.pkl', 'wb'))

Das ist alles mit der Wiederherstellung unvollständiger Daten.

Bulletin Update


Die Übersichtstabelle muss noch mit aktualisierten Daten zur Anzahl der Männer und Frauen usw. aktualisiert werden.

rs['total raw'] = rs['total']
rs['males raw'] = rs['males']
rs['females raw'] = rs['females']
rs['rus raw'] = rs['rus']

for i in rs.index:
    e = rs.loc[i,'event']
    rs.loc[i,'total'] = len(rd[e])
    rs.loc[i,'males'] = len(rd[e][rd[e]['sex'] == 'M'])
    rs.loc[i,'females'] = len(rd[e][rd[e]['sex'] == 'F'])
    rs.loc[i,'rus'] = len(rd[e][rd[e]['country'] == 'RUS'])

len(rs[rs['total'] != rs['total raw']])

Out: 288


len(rs[rs['males'] != rs['males raw']])

Out:962


len(rs[rs['females'] != rs['females raw']])

Out: 836


len(rs[rs['rus'] != rs['rus raw']])

Out: 8


pkl.dump(rs, open(r'D:\tri\summary6.pkl', 'wb'))

Teil 4. Probenahme


Jetzt ist der Triathlon sehr beliebt. Während der Saison gibt es viele offene Wettkämpfe, an denen eine große Anzahl von Athleten, hauptsächlich Amateure, teilnehmen. Das war aber nicht immer so. Unsere Daten enthalten seit 1990 Aufzeichnungen. Beim Durchblättern von tristats.ru habe ich festgestellt, dass es in den letzten Jahren viel mehr Rennen gibt und in den ersten nur sehr wenige. Aber jetzt, da unsere Daten vorbereitet wurden, können Sie sie genauer betrachten.

Zehn Jahre


Zählen Sie die Anzahl der Rennen und Finisher pro Jahr:

rs['year'] = pd.DatetimeIndex(rs['date']).year
years = range(rs['year'].min(),rs['year'].max())
rsy = pd.DataFrame(columns = ['races', 'finishers', 'rus', 'RUS'], index = years) #rsy – races summary by year

for y in rsy.index:
    rsy.loc[y,'races'] = len(rs[rs['year'] == y])
    rsy.loc[y,'finishers'] = sum(rs[rs['year'] == y]['total'])
    rsy.loc[y,'rus'] =  sum(rs[rs['year'] == y]['rus'])
    rsy.loc[y,'RUS'] = len(rs[(rs['year'] == y)&(rs['country'] == 'RUS')])

yearracesfinishersrusRUS
1990128650
19910000
1992131730
1993288730
1994212830
1995373170
1996377660
19973403110
19984583210
1999101106260
2000101231290
2001111992320
20022122491000
20033031521580
20041954881281
20051630242441
20062962103691
200744121534441
200843138303691
200949270474781
201047265283661
201177454128485
2012967559010554
2013988661721659
2014135138018318811
2015164172375484615
2016192178630754127
2017238185473882542
20182782030311095454
20192932209011335459
RUS - Rennen in Russland. Rus - Finisher aus Russland.

So sieht es auf einem Diagramm aus:


Es ist ersichtlich, dass die Anzahl der Rennen und Teilnehmer zu Beginn und am Ende des Zeitraums einfach nicht vergleichbar ist. Ein deutlicher Anstieg der Gesamtzahl der Rennen beginnt im Jahr 2011, während die Anzahl der Starts in Russland ebenfalls zunimmt. Darüber hinaus ist bereits 2009 ein Anstieg der Teilnehmerzahl zu beobachten. Dies kann auf ein erhöhtes Interesse der Teilnehmer hinweisen, dh auf eine erhöhte Nachfrage, wonach zwei Jahre später das Angebot, dh die Anzahl der Starts, zunahm. Vergessen Sie jedoch nicht, dass die Daten möglicherweise nicht vollständig sind und einige und möglicherweise viele Rennen fehlen. Einschließlich aufgrund der Tatsache, dass das Projekt zur Erfassung dieser Daten erst im Jahr 2010 begann, was auch den bedeutenden Sprung in der Grafik in diesem Moment erklären kann. Daher habe ich mich zur weiteren Analyse entschlossen, die letzten 10 Jahre zu nehmen. Dies ist eine ziemlich lange Zeit,Um Trends über mehrere Jahre hinweg zu verfolgen, obwohl sie kurz genug sind, um nicht dorthin zu gelangen, hauptsächlich professionelle Wettbewerbe aus den 90er und frühen 2000er Jahren.

rs = rs[(rs['year']>=2010)&(rs['year']<= 2019)]



Im ausgewählten Zeitraum fielen übrigens 84% ​​der Rennen und 94% der Finisher.

Amateur startet


Die überwiegende Mehrheit der Teilnehmer an den ausgewählten Starts sind Amateursportler, sodass gute Statistiken von ihnen erhalten werden können. Ehrlich gesagt war dies für mich von größtem Interesse, da ich selbst an solchen Starts teilnehme, aber im Level ist es sehr weit von den Olympiasiegern entfernt. Im ausgewählten Zeitraum fanden natürlich auch professionelle Wettbewerbe statt. Um die Indikatoren für Amateur- und Profirennen nicht zu mischen, wurde beschlossen, letztere nicht mehr zu berücksichtigen. Wie identifiziere ich sie? Nach Geschwindigkeit. Wir berechnen sie. In einer der ersten Phasen der Datenaufbereitung haben wir bereits festgestellt, welche Art von Distanz bei jedem Rennen vorhanden ist - Sprint, Olympia, Halb, Eisen. Für jeden von ihnen ist der Kilometerstand der Etappen klar definiert - Schwimmen, Radfahren und Laufen. Dies sind 0,75 + 20 + 5 für den Sprint, 1,5 + 40 + 10 für die Olympischen Spiele, 1,9 + 90 + 21,1 für die Hälfte und 3.8 + 180 + 42,2 für Eisen. Natürlich können die realen Zahlen für jeden Typ von Rennen zu Rennen bedingt bis zu einem Prozent variieren, aber es gibt keine Informationen darüber, daher gehen wir davon aus, dass alles korrekt war.

rs['km'] = ''

rs.loc[rs['dist'] == 'sprint', 'km'] = 0.75+20+5
rs.loc[rs['dist'] == 'olympic', 'km'] = 1.5+40+10
rs.loc[rs['dist'] == 'half', 'km'] = 1.9+90+21.1
rs.loc[rs['dist'] == 'full', 'km'] = 3.8+180+42.2

Wir berechnen die Durchschnitts- und Höchstgeschwindigkeit für jedes Rennen. Das Maximum bezieht sich hier auf die Durchschnittsgeschwindigkeit des Athleten, der den ersten Platz gewonnen hat.

for index, row in rs.iterrows():
    e = row['event']
    rd[e]['th'] = pd.TimedeltaIndex(rd[e]['result']).seconds/3600
    rd[e]['v'] = rs.loc[i, 'km'] / rd[e]['th']

for index, row in rs.iterrows():
    e = row['event']
    rs.loc[index,'vmax'] = rd[e]['v'].max()
    rs.loc[index,'vavg'] = rd[e]['v'].mean()



Nun, Sie können sehen, dass der Großteil der Geschwindigkeiten in Haufen zwischen etwa 15 km / h und 30 km / h gesammelt wird, aber es gibt eine bestimmte Anzahl vollständig "kosmischer" Werte. Sortieren Sie nach Durchschnittsgeschwindigkeit und sehen Sie, wie viele davon:

rs = rs.sort_values(by='vavg')



Hier haben wir die Skala geändert und können den Bereich genauer abschätzen. Bei Durchschnittsgeschwindigkeiten liegt sie zwischen 17 km / h und 27 km / h, bei Höchstgeschwindigkeiten zwischen 18 km / h und 32 km / h. Außerdem gibt es „Schwänze“ mit sehr niedrigen und sehr hohen Durchschnittsgeschwindigkeiten. Niedrige Geschwindigkeiten entsprechen höchstwahrscheinlich extremen Wettkämpfen wie Norseman , und hohe Geschwindigkeiten können im Fall eines abgebrochenen Schwimmens auftreten, bei dem anstelle eines Sprints ein Supersprint stattfand oder einfach fehlerhafte Daten. Ein weiterer wichtiger Punkt ist der sanfte Schritt im 1200- Bereich entlang der X- Achseund höhere Werte der Durchschnittsgeschwindigkeit danach. Dort sehen Sie einen deutlich geringeren Unterschied zwischen Durchschnitts- und Höchstgeschwindigkeit als in den ersten zwei Dritteln des Diagramms. Anscheinend ist dies ein professioneller Wettbewerb. Um sie klarer zu unterscheiden, berechnen wir das Verhältnis von Höchstgeschwindigkeit zu Durchschnitt. Bei professionellen Wettbewerben, bei denen es keine zufälligen Personen gibt und alle Teilnehmer eine sehr hohe körperliche Fitness haben, sollte dieses Verhältnis minimal sein.

rs['vmdbva'] = rs['vmax']/rs['vavg'] #vmdbva - v max divided by v avg
rs = rs.sort_values(by='vmdbva')



In dieser Grafik fällt das erste Quartal sehr deutlich auf: Das Verhältnis von Höchstgeschwindigkeit zu Durchschnitt ist klein, hohe Durchschnittsgeschwindigkeit, eine kleine Anzahl von Teilnehmern. Dies ist ein professioneller Wettbewerb. Der Schritt auf der grünen Kurve liegt irgendwo bei 1,2. Wir werden in unserer Stichprobe nur Datensätze mit einem Verhältniswert größer als 1,2 belassen.

rs = rs[rs['vmdbva'] > 1.2]

Wir entfernen auch Datensätze mit atypisch niedrigen und hohen Geschwindigkeiten. In Was sind die Triathlon-Weltrekorde für jede Distanz? veröffentlichte Rekordzeiten für das Überqueren verschiedener Entfernungen für 2019. Wenn Sie sie mit mittlerer Geschwindigkeit zählen, können Sie sehen, dass sie selbst für die schnellsten nicht höher als 33 km / h sein kann. Daher werden wir die Protokolle berücksichtigen, bei denen die Durchschnittsgeschwindigkeiten höher und ungültig sind, und sie aus der Betrachtung entfernen.

rs = rs[(rs['vavg'] > 17)&(rs['vmax'] < 33)]

Folgendes bleibt:



Jetzt sieht alles ziemlich homogen aus und wirft keine Fragen auf. Infolge all dieser Auswahl haben wir 777 der Protokolle von 1922 oder 40% verloren. Gleichzeitig ging die Gesamtzahl der Finisher nicht so stark zurück - nur um 13%.

Es sind also noch 1.145 Rennen mit 1.231.772 Finishern übrig. Diese Probe wurde zum Material für meine Analyse und Visualisierung.

Teil 5. Analyse und Visualisierung


In dieser Arbeit waren die eigentliche Analyse und Visualisierung die einfachsten Teile. Die Spitze des Eisbergs, dessen Unterwasserteil nur die Aufbereitung der Daten war. Die Analyse war in der Tat eine einfache arithmetische Operation für die Pandas-Reihe , Berechnung von Durchschnittswerten, Filterung - all dies wird von den elementaren Pandas- Werkzeugen durchgeführt, und der obige Code enthält viele Beispiele. Die Visualisierung erfolgte wiederum hauptsächlich mit der Standard-Matplotlib . Gebrauchte Handlung, Bar, Kuchen . An einigen Stellen musste ich jedoch bei Datumsangaben und Piktogrammen an den Signaturen der Achsen basteln, aber dies ist hier nicht eine detaillierte Beschreibung. Das einzige, worüber man sprechen sollte, ist die Präsentation von Geodaten. Zumindest ist es nichtmatplotlib .

Geodaten


Für jedes Rennen haben wir Informationen über den Austragungsort. Zu Beginn haben wir mithilfe von Geopy die Koordinaten für jeden Standort berechnet. Viele Rennen finden jährlich am selben Ort statt. Ein sehr praktisches Werkzeug zum Rendern von Geodaten in Python ist Folium . So funktioniert das:
eventdatecountrylatitudelongitudeloc
0Ironman Indian Wells La Quinta 70.3 20192019-12-08USA33.7238-116.305Indian Wells/La Quinta, California, USA
1Ironman Taupo 70.3 20192019-12-07NZL-41.5001172.834New Zealand
2Ironman Western Australia 20192019-12-01AUS-33.6445115.349Busselton, Western Australia
3Ironman Mar del Plata 20192019-12-01ARG-37.9977-57.5483Mar del Plata, Argentina
4Ironman Cozumel 20192019-11-24MEX20.4318-86.9203Cozumel, Mexico
5Ironman Arizona 20192019-11-24USA33.4255-111.94Tempe, Arizona, USA
6Ironman Xiamen 70.3 20192019-11-10CHN24.4758118.075Xiamen, China
7Ironman Turkey 70.3 20192019-11-03TUR36.863331.0578Belek, Antalya, Turkey
8Ironman Florida 20192019-11-02USA30.1766-85.8055Panama City Beach, Florida, USA
9Ironman Marrakech 70.3 20192019-10-27MAR31.6258-7.98916Marrakech, Morocco
10Ironman Waco 70.3 20192019-10-27USA31.5493-97.1467Waco, Texas, USA


import folium

m = folium.Map() 
folium.Marker(['55.7522200', '37.6155600'], popup='').add_to(m)

Und wir erhalten eine interaktive Karte direkt im Jupiter-Laptop.



Nun zu unseren Daten. Zunächst beginnen wir eine neue Spalte mit einer Kombination unserer Koordinaten:

rs['coords'] = rs['latitude'].astype(str) + ', ' + rs['longitude'].astype(str)

Die einzigartigen Koordinaten Koordinaten sind 291. Und die einzigartigen Orte loc sind 324, was bedeutet , dass einige Namen geringfügig voneinander verschieden sind, während zur gleichen Zeit , die sie auf den gleichen Punkt entsprechen. Es ist nicht beängstigend, wir werden die Einzigartigkeit durch Koordinaten betrachten . Wir berechnen, wie viele Ereignisse über die gesamte Zeit an jedem Ort vergangen sind (mit eindeutigen Koordinaten):

vc = rs['coords'].value_counts()

vc

Out:
43.7009358, 7.2683912 22
43.5854823, 39.723109 20
29.03970805, -13.636291 16
47.3723941, 8.5423328 16
59.3110918, 24.420907 15
51.0834196, 10.4234469 15
54.7585694, 38.8818137 14
20.4317585, -86.9202745 13
52.3727598, 4.8936041 12
41.6132925, 2.6576102 12
... ...

Erstellen Sie nun eine Karte und fügen Sie Markierungen in Form von Kreisen hinzu, deren Radius von der Anzahl der Ereignisse am Standort abhängt. Fügen Sie den Markierungen Markierungen mit Standortnamen hinzu.

m = folium.Map(location=[25,10], zoom_start=2)

for c in rs['coords'].unique():
    row = [r[1] for r in rs.iterrows() if r[1]['coords'] == c][0]    
    folium.Circle([row['latitude'], row['longitude']], 
					popup=(row['location']+'\n('+str(vc[c])+' races)'), 
					radius = 10000*int(vc[c]), 
					color='darkorange', 
					fill=True, 
					stroke=True, 
					weight=1).add_to(m)

Erledigt. Sie können das Ergebnis sehen:



Teilnehmer Fortschritt


Zusätzlich zur Anleitung war die Arbeit an einem weiteren Zeitplan nicht trivial. Dies ist das neueste Fortschrittsdiagramm der Teilnehmer. Hier ist es:



Lassen Sie es uns analysieren, gleichzeitig werde ich den Code zum Rendern als Beispiel für die Verwendung von matplotlib geben :

fig = plt.figure()
fig.set_size_inches(10, 6)

ax = fig.add_axes([0,0,1,1])

b = ax.bar(exp,numrecs, color = 'navajowhite')

ax1 = ax.twinx()

for i in range(len(exp_samp)):
    ax1.plot(exp_samp[i], vproc_samp[i], '.')
	
p, = ax1.plot(exp, vpm, 'o-',markersize=8, linewidth=2, color='C0')

for i in range(len(exp)):
    if i < len(exp)-1 and (vpm[i] < vpm[i+1]):
        ax1.text(x = exp[i]+0.1, y = vpm[i]-0.2, s = '{0:3.1f}%'.format(vpm[i]),size=12)
    else:
        ax1.text(x = exp[i]+0.1, y = vpm[i]+0.1, s = '{0:3.1f}%'.format(vpm[i]),size=12)

ax.legend((b,p), (' ', ''),loc='center right')
ax.set_xlabel('   ')
ax.set_ylabel('')
ax1.set_ylabel('%     ')
ax.set_xticks(np.arange(1, 11, step=1))
ax.set_yticks(np.arange(0, 230000, step=25000))
ax1.set_ylim(97.5,103.5)
ax.yaxis.set_label_position("right")
ax.yaxis.tick_right()
ax1.yaxis.set_label_position("left")
ax1.yaxis.tick_left()      

plt.show()

Nun darüber, wie die Daten für ihn berechnet wurden. Zunächst mussten Sie die Namen der Teilnehmer auswählen, die mindestens zwei Rennen und in verschiedenen Kalenderjahren abgeschlossen haben und gleichzeitig keine Profis sind.

Füllen Sie zunächst für jedes Protokoll eine neue Spalte mit dem Namen Datum aus , in der das Datum des Rennens angegeben ist. Wir werden auch ein Jahr ab diesem Zeitpunkt benötigen, werden wir die Säule machen Jahr . Da wir die Geschwindigkeit jedes Athleten im Verhältnis zur Durchschnittsgeschwindigkeit im Rennen analysieren werden, berechnen wir diese Geschwindigkeit sofort in der neuen Spalte vproc - die Geschwindigkeit als Prozentsatz des Durchschnitts.

for index, row in rs.iterrows():
    e = row['event']
    rd[e]['date'] = row['date']
    rd[e]['year'] = row['year']
    rd[e]['vproc'] = 100 * rd[e]['v'] / rd[e]['v'].mean()

So sehen die Protokolle jetzt aus: Kombinieren Sie als Nächstes alle Protokolle in einem einzigen Datenrahmen.
' Sprint 2019'
placesexnamecountry...thvdateyearvproc
01MShalev AlekseyRUS...1.16194422.1611282019-09-142019130.666668
12MNikolaev ArtemRUS...1.22861120.9586252019-09-142019123.576458
23MKuchierskiy AleksandrRUS...1.25555620.5088502019-09-142019120.924485
34FKorchagina MariyaRUS...1.29722219.8501072019-09-142019117.040401
45MSolodov IvanRUS...1.29805619.8373642019-09-142019116.965263
56MBukin SergeyRUS...1.30027819.8034612019-09-142019116.765365
67MLavrentev DmitriyRUS...1.30027819.8034612019-09-142019116.765365
78MDolgov PetrRUS...1.32166719.4829762019-09-142019114.875719
89MBezruchenko MikhailnRUS...1.34500019.1449812019-09-142019112.882832
910MRyazantsev DmitriyRUS...1.35944418.9415612019-09-142019111.683423
1011MIbragimov RamilRUS...1.37638918.7083752019-09-142019110.308511



ar = pd.concat(rd)

Für jeden Teilnehmer hinterlassen wir in jedem Kalenderjahr nur einen Eintrag:

ar1 = ar.drop_duplicates(subset = ['name','year'], keep='first')

Als nächstes finden wir aus allen eindeutigen Namen dieser Einträge diejenigen, die mindestens zweimal vorkommen:

nvc = ar1['name'].value_counts()
names = list(nvc[nvc > 1].index)

Es gibt 219.890 von ihnen. Entfernen wir die Namen von Profisportlern aus dieser Liste:

pro_names = ar[ar['group'].isin(['MPRO','FPRO'])]['name'].unique()
names = list(set(names) - set(pro_names))

Sowie die Namen der Athleten, die vor 2010 mit dem Auftritt begonnen haben. Laden Sie dazu die Daten hoch, die vor der Stichprobe in den letzten 10 Jahren gespeichert wurden. Fügen Sie sie in die Objekte rsa ( Rassenübersicht alle) und rda ( Renndetails alle) ein.

rdo = {} 

for e in rda:    
    if rsa[rsa['event'] == e]['year'].iloc[0] < 2010:
        rdo[e] = rda[e]

aro = pd.concat(rdo)
old_names = aro['name'].unique()
names = list(set(names) - set(old_names))

Und schließlich finden wir Namen, die mehr als einmal am selben Tag vorkommen. Daher minimieren wir das Vorhandensein vollständiger Namensvetter in unserer Stichprobe.

namesakes = ar[ar.duplicated(subset = ['name','date'], keep = False)]['name'].unique()
names = list(set(names) - set(namesakes))

Es sind also noch 198.075 Namen übrig. Aus dem gesamten Datensatz wählen wir nur die Datensätze mit den gefundenen Namen aus:

ars = ar[ar['name'].isin(names)] #ars – all recrds selected

Jetzt müssen Sie für jeden Rekord bestimmen, welchem ​​Jahr in der Karriere des Athleten er entspricht - dem ersten, zweiten, dritten oder zehnten. Wir machen eine Schleife mit allen Namen und berechnen.

ars['exp'] = '' #exp – experience, counted in years of racing, starts from 1.

for n in names:    
    ind = ars[ars['name'] == n].index 
    yos = ars.loc[ind, 'year'].min() #yos – year of start
    ars.loc[ind, 'exp'] = ars.loc[ind, 'year'] - yos + 1 

Hier ist ein Beispiel dafür, was passiert ist: Anscheinend sind die Namensvetter immer noch geblieben. Dies wird erwartet, ist aber nicht beängstigend, da wir alles mitteln werden und es nicht so viele geben sollte. Als nächstes erstellen wir Arrays für das Diagramm:
eventplacesexnamecountrygroupthvdateyearvprocexp
633MGolovin SergeyRUSM40-445.35611121.0973972014-08-312014106.0368791
302MGolovin SergeyRUSM40-4411.23638920.1132232015-08-302015108.2312542
522MGolovin SergeyRUSM40-4410.40277821.7249672016-07-172016111.2651073
25MGolovin SergeyRUSM40-4410.91083320.7133582017-09-232017112.9536444
23MGolovin SergeyRUSM40-444.70000024.0425532017-06-032017120.5652114
42MGolovin SergeyRUSM40-444.59916724.5696682018-06-172018124.5798625
90MGolovin SergeyNOR14.06916716.0634962018-08-042018100.0018345
86MGolovin SergeyRUSM45-499.82055623.0129552019-08-032019118.3757666



exp = [] 
vpm = [] #vpm – v proc mean
numrecs = [] #number of records

for x in range(ars['exp'].min(), ars['exp'].max() + 1): 
    exp.append(x)
    vpm.append(ars[ars['exp'] == x]['vproc'].mean())
    numrecs.append(len(ars[ars['exp'] == x]))

Das war's, es gibt eine Basis:



Um es nun mit Punkten zu dekorieren, die bestimmten Ergebnissen entsprechen, werden wir 1000 zufällige Namen auswählen und Arrays mit den Ergebnissen für sie erstellen.

names_samp = random.sample(names,1000)
ars_samp = ars[ars['name'].isin(names_samp)]

ars_samp = ars_samp.reset_index(drop = True)
exp_samp = []
vproc_samp = []

for n in names_samp:
    nr = ars_samp[ars_samp['name'] == n]
    nr = nr.sort_values('exp')
    exp_samp.append(list(nr['exp']))
    vproc_samp.append(list(nr['vproc']))

Fügen Sie eine Schleife hinzu, um Diagramme aus dieser Zufallsstichprobe zu erstellen.

for i in range(len(exp_samp)):
    ax1.plot(exp_samp[i], vproc_samp[i], '.')

Jetzt ist alles fertig:



Im Allgemeinen ist es nicht schwierig. Es gibt jedoch ein Problem. Um die Exp- Erfahrung in einem Zyklus zu berechnen, benötigen alle Namen, die fast 200.000 sind, acht Stunden. Ich musste den Algorithmus für kleine Stichproben debuggen und dann die Berechnung für die Nacht ausführen. Im Prinzip kann dies einmal durchgeführt werden. Wenn Sie jedoch einen Fehler finden oder etwas ändern möchten und ihn erneut nachzählen müssen, beginnt er sich zu belasten. Als ich am Abend einen Bericht veröffentlichen wollte, stellte sich heraus, dass es wieder notwendig war, alles noch einmal zu erzählen. Das Warten bis zum Morgen war nicht Teil meiner Pläne, und ich suchte nach einer Möglichkeit, die Berechnung zu beschleunigen. Beschlossen zu parallelisieren.

Ich habe irgendwo einen Weg gefunden, dies mit Multiprocessing zu tun. Um unter Windows arbeiten zu können, mussten wir die Hauptlogik jeder parallelen Aufgabe in einer separaten Datei worker.py ablegen :

import pickle as pkl

def worker(args):
    names = args[0]
    ars=args[1]
    num=args[2]
    ars = ars.sort_values(by='name')
    ars = ars.reset_index(drop=True)  

    for n in names:   
        ind = ars[ars['name'] == n].index 
        yos = ars.loc[ind, 'year'].min()
        ars.loc[ind, 'exp'] = ars.loc[ind, 'year'] - yos + 1                      

    with open(r'D:\tri\par\prog' + str(num) + '.pkl', 'wb') as f:
        pkl.dump(ars,f)

Das Verfahren auf einen Teil der Namen übertragen Namen , datafreyma Teil ar nur mit diesen Namen und die Seriennummer der parallelen Aufgaben - num . Berechnungen werden in den Datenrahmen geschrieben und am Ende wird der Datenrahmen in die Datei geschrieben. In dem Laptop, der diesen Worker aufruft , bereiten wir die Argumente entsprechend vor:

num_proc = 8 #number of processors
args = []

for i in range(num_proc):
    step = int(len(names_samp)/num_proc) + 1
    names_i = names_samp[i*step:min((i+1)*step, len(names_samp))]
    ars_i = ars[ars['name'].isin(names_i)]
    args.append([names_i, ars_i, i])

Wir beginnen mit dem parallelen Rechnen:

from multiprocessing import Pool
import workers

if __name__ ==  '__main__':     
    p=Pool(processes = num_proc)
    p.map(workers.worker,args)

Und am Ende lesen wir die Ergebnisse aus den Dateien und sammeln die Teile wieder im gesamten Datenrahmen:

ars=pd.DataFrame(columns = ars.columns)

for i in range(num_proc):
    with open(r'D:\tri\par\prog'+str(i)+'.pkl', 'rb') as f:    
        arsi = pkl.load(f)
        print(len(arsi))
        ars = pd.concat([ars, arsi])

Somit war es möglich, eine Beschleunigung von 40 zu erhalten und anstelle von 8 Stunden die Berechnung in 11 Minuten abzuschließen und an diesem Abend einen Bericht zu veröffentlichen. Gleichzeitig habe ich gelernt, wie man in Python parallelisiert . Ich denke, das wird nützlich sein. Hier stellte sich heraus, dass die Beschleunigung mehr als das Achtfache der Anzahl der Kerne betrug, da für jede Aufgabe ein kleiner Datenrahmen verwendet wurde, was die Suche beschleunigt. Im Prinzip könnten sequentielle Berechnungen auf diese Weise beschleunigt werden, aber die Frage ist, wie raten Sie?

Ich konnte mich jedoch nicht beruhigen und dachte auch nach der Veröffentlichung ständig darüber nach, wie die Berechnung mithilfe der Vektorisierung durchgeführt werden sollte, dh Operationen an ganzen Spalten des Datenrahmens der Pandas-Serie. Solche Berechnungen sind selbst auf einem Supercluster um eine Größenordnung schneller als alle parallelisierten Zyklen. Und kam mit. Es stellt sich heraus, dass es für jeden Namen im Gegenteil notwendig ist, das Jahr des Beginns einer Karriere zu finden - für jedes Jahr die Teilnehmer zu finden, die damit begonnen haben. Dazu müssen Sie zunächst alle Namen für das erste Jahr aus unserer Stichprobe ermitteln, dies ist 2010. Dementsprechend verarbeiten wir alle Datensätze mit diesen Namen in diesem Jahr. Als nächstes nehmen wir das nächste Jahr - 2011.

Auch in diesem Jahr finden wir wieder alle Namen mit Einträgen, aber wir nehmen nur unverarbeitete Namen von ihnen, dh diejenigen, die 2010 nicht erfüllt wurden und 2011 mit ihnen verarbeitet werden. Und so weiter für den Rest des Jahres. Der gleiche Zyklus, aber nicht zweihunderttausend Iterationen, sondern insgesamt neun.

for y in range(ars['year'].min(),ars['year'].max()):
    arsynp = ars[(ars['exp'] == '') & (ars['year'] == y)] #arsynp - all records selected for year not processed
    namesy = arsynp['name'].unique()
    ind = ars[ars['name'].isin(namesy)].index
    ars.loc[ind, 'exp'] = ars.loc[ind,'year'] - y + 1

Dieser Zyklus dauert nur wenige Sekunden. Und der Code erwies sich als viel prägnanter.

Fazit


Nun, endlich ist eine Menge Arbeit erledigt. Für mich war dies tatsächlich das erste Projekt dieser Art. Als ich es nahm, war das Hauptziel, die Verwendung von Python und seinen Bibliotheken zu üben. Diese Aufgabe ist mehr als abgeschlossen. Und die Ergebnisse selbst waren durchaus vorzeigbar. Welche Schlussfolgerungen habe ich nach Abschluss für mich gezogen?

Erstens: Daten sind unvollständig. Dies gilt wahrscheinlich für fast jede Analyseaufgabe. Selbst wenn sie vollständig strukturiert sind und dies häufig anders geschieht, müssen Sie darauf vorbereitet sein, sie zu basteln, bevor Sie mit der Berechnung der Merkmale und der Suche nach Trends beginnen - Fehler, Ausreißer, Abweichungen von Standards usw. finden.

Zweitens:Jede Aufgabe hat eine Lösung. Dies ist eher ein Slogan, aber oft ist es so. Es ist nur so, dass diese Lösung möglicherweise nicht so offensichtlich ist und nicht in den Daten selbst liegt, sondern sozusagen außerhalb des Rahmens. Als Beispiel - die Verarbeitung der Namen der oben beschriebenen Teilnehmer oder das Scraping der Website.

Drittens: Domänenwissen ist entscheidend. Dies ermöglicht es, die Daten besser vorzubereiten, offensichtlich ungültige oder nicht standardmäßige Daten zu entfernen, Interpretationsfehler zu vermeiden, Informationen zu verwenden, die nicht in den Daten enthalten sind, z. B. Entfernungen in diesem Projekt, die Ergebnisse in der in der Community akzeptierten Form darzustellen und dumme, falsche Schlussfolgerungen zu vermeiden.

Viertens: In Python arbeitenEs gibt eine Vielzahl von Werkzeugen. Manchmal scheint es wert zu sein, über etwas nachzudenken, man beginnt zu suchen - es existiert bereits. Das ist einfach toll! Vielen Dank an die Macher für diesen Beitrag, insbesondere für die Werkzeuge, die sich hier als nützlich erwiesen haben: Selen zum Schaben, Pycountry zur Bestimmung des Ländercodes gemäß ISO-Standard, Ländercodes (Datahub) für olympische Codes, Geopy zur Bestimmung der Koordinaten an der Adresse, Folium - zur Visualisierung von Geodaten, Gender-Guesser - zur Analyse von Namen, Multiprocessing - für Parallel Computing, Matplotlib , Numpy und natürlich Pandas - ohne sie gibt es keinen Ort , an den man gehen kann.

Fünftens: Vektorisierung ist unser Alles. Es ist äußerst wichtig, die eingebauten Pandas- Tools verwenden zu können , es ist sehr effektiv. Ich nehme an, in den meisten Fällen, wenn die Anzahl der Datensätze ab Zehntausenden gemessen wird, wird diese Fähigkeit einfach notwendig.

Sechste:Der Umgang mit Daten ist eine schlechte Idee. Es muss versucht werden, manuelle Eingriffe so gering wie möglich zu halten. Erstens wird nicht skaliert, dh wenn sich die Datenmenge mehrmals erhöht, erhöht sich die Zeit für die manuelle Verarbeitung auf inakzeptable Werte, und zweitens wird die Wiederholbarkeit schlecht sein. Sie werden etwas vergessen und irgendwo einen Fehler machen . Alles ist nur programmatisch, wenn etwas vom allgemeinen Standard für eine Softwarelösung abweicht, ist es in Ordnung, Sie können einen Teil der Daten opfern, es wird immer noch mehr Pluspunkte geben.

Siebte:Der Code muss funktionsfähig bleiben. Es scheint, dass es offensichtlicher sein könnte! Wenn es um Code für den eigenen Gebrauch geht, dessen Zweck darin besteht, die Ergebnisse dieses Codes zu veröffentlichen, ist hier nicht alles so streng. Ich habe in Jupiter Notebooks gearbeitet, und diese Umgebung muss meiner Meinung nach keine integralen Softwareprodukte erstellen. Es ist für den zeilenweisen, stückweisen Start konfiguriert. Dies hat seine Vorteile - es ist schnell: Entwicklung, Debugging und Ausführung gleichzeitig. Aber oft besteht die Versuchung darin, nur eine Zeile zu bearbeiten und schnell ein neues Ergebnis zu erhalten, anstatt def zu duplizieren oder einzuwickeln. Natürlich sollte eine solche Versuchung vermieden werden. Es ist notwendig, nach einem guten Code zu streben, auch „für sich selbst“, zumindest weil der Start selbst für eine Analysearbeit viele Male durchgeführt wird und sich die Investition von Zeit am Anfang sicherlich in Zukunft auszahlen wird. Und Sie können Tests auch auf Laptops in Form von Überprüfungen kritischer Parameter und Auslösen von Ausnahmen hinzufügen - dies ist sehr nützlich.

Achte: Sparen Sie öfter. Bei jedem Schritt habe ich eine neue Version der Datei gespeichert. Insgesamt stellte sich heraus, dass sie ungefähr 10 waren. Dies ist praktisch, da es bei der Erkennung eines Fehlers hilfreich ist, schnell festzustellen, in welchem ​​Stadium er aufgetreten ist. Außerdem habe ich die Quelldaten in den als roh gekennzeichneten Spalten gespeichert. Auf diese Weise können Sie das Ergebnis schnell überprüfen und die Diskrepanz feststellen.

Neunte:Es ist notwendig, den Zeitaufwand und das Ergebnis zu messen. An einigen Stellen habe ich sehr lange gebraucht, um Daten wiederherzustellen, die einen Bruchteil eines Prozentsatzes der Gesamtmenge ausmachen. Tatsächlich machte das keinen Sinn, man musste sie einfach wegwerfen, und das ist alles. Und ich würde es tun, wenn es ein kommerzielles Projekt wäre, kein Autotraining. Dies würde es Ihnen ermöglichen, das Ergebnis viel schneller zu erhalten. Hier funktioniert das Pareto-Prinzip - 80% des Ergebnisses werden in 20% der Fälle erzielt.

Und der Letzte:Die Arbeit an solchen Projekten erweitert den Horizont erheblich. Bereitwillig lernen Sie etwas Neues - zum Beispiel die Namen fremder Länder - wie die Pitcairn-Inseln, dass der ISO-Code für die Schweiz CHE ist, aus dem lateinischen „Confoederatio Helvetica“, was der spanische Name eigentlich über den Triathlon selbst ist - Aufzeichnungen, ihre Besitzer, Orte von Rassen, Geschichte von Ereignissen und so weiter.

Vielleicht genug. Das ist alles. Vielen Dank an alle, die bis zum Ende gelesen haben!

All Articles