بيثون رائع تعليمي معالجة البيانات الرياضية



في العامين الماضيين في وقت فراغي كنت أقوم بممارسة الترياتلون. تحظى هذه الرياضة بشعبية كبيرة في العديد من دول العالم ، وخاصة في الولايات المتحدة الأمريكية وأستراليا وأوروبا. تكتسب حاليًا شعبية سريعة في روسيا ودول رابطة الدول المستقلة. إنه يتعلق بإشراك الهواة وليس المحترفين. على عكس مجرد السباحة في المسبح وركوب الدراجات والركض في الصباح ، تتضمن الترياتلون المشاركة في المسابقات والتحضير المنتظم لها ، حتى دون أن تكون محترفًا. بالتأكيد بين أصدقائك هناك بالفعل "رجل حديدي" واحد على الأقل أو شخص يخطط ليصبح واحدًا. ضخمة ، مجموعة متنوعة من المسافات والظروف ، ثلاث رياضات في واحدة - كل هذا لديه القدرة على تكوين كمية كبيرة من البيانات. في كل عام ، تجري عدة مئات من مسابقات الترياتلون في العالم ، حيث يشارك فيها مئات الآلاف من الأشخاص.تقام المسابقات من قبل العديد من المنظمين. كل منهم ، بالطبع ، ينشر النتائج في حد ذاته. ولكن بالنسبة للرياضيين من روسيا وبعض دول رابطة الدول المستقلة ، الفريقتجمع tristats.ru جميع النتائج في مكان واحد - على موقعها الإلكتروني الذي يحمل نفس الاسم. هذا يجعل من السهل جدًا البحث عن النتائج ، سواء الخاصة بك أو أصدقائك ومنافسيك ، أو حتى أصنامك. ولكن بالنسبة لي ، فقد أعطت الفرصة أيضًا لتحليل عدد كبير من النتائج برمجيًا. النتائج المنشورة على trilife: قراءة .

كان هذا هو أول مشروع لي من هذا النوع ، لأنني بدأت مؤخرًا فقط في إجراء تحليل البيانات من حيث المبدأ ، وكذلك استخدام الثعبان. لذلك ، أود أن أخبركم عن التنفيذ الفني لهذا العمل ، خاصة أنه في هذه العملية ، ظهرت فروق دقيقة مختلفة ، تتطلب أحيانًا نهجًا خاصًا. ستتمحور حول التخريد والتحليل وأنواع الصب والتنسيقات واستعادة البيانات غير المكتملة وإنشاء عينة تمثيلية والتصور والتحويل وحتى الحوسبة المتوازية.

تحول الحجم إلى حجم كبير ، لذلك قمت بتقسيم كل شيء إلى خمسة أجزاء حتى أتمكن من أخذ المعلومات وتذكر من أين أبدأ بعد الفاصل.

قبل المضي قدمًا ، من الأفضل أن تقرأ مقالتي أولاً بنتائج الدراسة ، لأنه هنا وصف المطبخ بشكل أساسي لإنشائه. يستغرق 10-15 دقيقة.

هل قرأت؟ إذا دعنا نذهب!

الجزء 1. الكشط والتحليل


تعطى: موقع tristats.ru . هناك نوعان من الجداول التي تهمنا. هذا في الواقع جدول ملخص لجميع الأجناس وبروتوكول لنتائج كل منها.





كانت المهمة الأولى هي الحصول على هذه البيانات برمجيًا وحفظها لمزيد من المعالجة. حدث ذلك أنني في ذلك الوقت كنت جديدًا في تقنيات الويب ، وبالتالي لم أكن أعرف على الفور كيفية القيام بذلك. لقد بدأت وفقًا لما عرفته - انظر إلى رمز الصفحة. يمكن القيام بذلك باستخدام زر الفأرة الأيمن أو مفتاح F12 .



تحتوي القائمة في Chrome على خيارين: عرض رمز الصفحة وعرض الرمز . ليس الانقسام الأكثر وضوحا. بطبيعة الحال ، يعطون نتائج مختلفة. تلك التي تعرض الرمز، إنها تمامًا مثل F12 - النص النصي المباشر بتنسيق html- لما يتم عرضه في المتصفح هو عنصر حكيم.



في المقابل ، عرض كود الصفحة يعطي كود المصدر للصفحة. أيضًا html ، ولكن لا توجد بيانات هناك ، فقط أسماء نصوص JS التي تفرغها. حسنا.



الآن نحن بحاجة إلى فهم كيفية استخدام python لحفظ رمز كل صفحة كملف نصي منفصل. أحاول هذا:

import requests

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

وأحصل على ... كود المصدر. لكني أحتاج إلى نتيجة تنفيذها. بعد الدراسة والبحث والسؤال ، أدركت أنني بحاجة إلى أداة لأتمتة إجراءات المتصفح ، على سبيل المثال ، السيلينيوم . وضعتها. وأيضًا ChromeDriver للعمل مع Google Chrome . ثم استخدمته على النحو التالي:

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()

يعمل هذا الرمز على تشغيل نافذة متصفح وفتح صفحة فيه على عنوان URL المحدد. ونتيجة لذلك ، نحصل على كود html بالفعل مع البيانات المطلوبة. ولكن هناك عقبة واحدة. والنتيجة 100 إدخال فقط ، ويبلغ إجمالي عدد السباقات 2000 تقريبًا. كيف ذلك؟ والحقيقة هي أنه في البداية يتم عرض أول 100 إدخال فقط في المتصفح ، وفقط إذا قمت بالتمرير إلى أسفل الصفحة تمامًا ، فسيتم تحميل المائة التالية ، وهكذا. لذلك ، من الضروري تنفيذ التمرير برمجياً. للقيام بذلك ، استخدم الأمر:

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

ومع كل تمرير ، سنتحقق مما إذا كان رمز الصفحة المحملة قد تغير أم لا. إذا لم تتغير ، فسنقوم بالتحقق عدة مرات من الموثوقية ، على سبيل المثال 10 ، ثم يتم تحميل الصفحة بأكملها ويمكنك التوقف. بين المخطوطات ، نقوم بتعيين المهلة على ثانية واحدة بحيث يكون للصفحة وقت للتحميل. (حتى لو لم يكن لديها الوقت ، لدينا احتياطي - تسع ثوان أخرى).

وسيبدو الرمز الكامل كما يلي:

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()

لذلك ، لدينا ملف html مع جدول ملخص لجميع الأجناس. بحاجة لتحليلها. للقيام بذلك ، استخدم مكتبة lxml .

from lxml import html

أولاً نجد جميع صفوف الجدول. لتحديد علامة سلسلة ، ما عليك سوى إلقاء نظرة على ملف html في محرر النصوص.



يمكن أن يكون ، على سبيل المثال ، "tr ng-تكرار = 'r في racesData' class = 'ng-نطاق' ' أو جزء لا يمكن العثور عليه في أي علامات.

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']")

ثم نبدأ dataframe pandas وكل عنصر من كل صف من الجدول مكتوب على هذا dataframe.

import pandas as pd

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

لمعرفة مكان إخفاء كل عنصر محدد ، ما عليك سوى إلقاء نظرة على رمز html لأحد عناصر صفوفنا في محرر النصوص نفسه.

<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>

أسهل طريقة للتنقل باستخدام رمز ثابت للأطفال هنا هي أنه لا يوجد الكثير منها.

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()

ها هي النتيجة: حفظ إطار البيانات هذا في ملف. يمكنني استخدام المخلل ، ولكن يمكن أن يكون CSV ، أو أي شيء آخر.
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)

في هذه المرحلة ، تكون جميع البيانات من نوع سلسلة. سنتحول لاحقًا. أهم شيء نحتاجه الآن هو الروابط. سنستخدمها لإلغاء بروتوكولات جميع الأجناس. نصنعها في الصورة وشبه كيف تم إجراؤها للجدول المحوري. في الدورة لجميع السباقات لكل منها ، سنفتح الصفحة حسب المرجع ، وننتقل ونحصل على رمز الصفحة. في جدول الملخص لدينا معلومات عن العدد الإجمالي للمشاركين في السباق - المجموع، سنستخدمها لنفهم حتى النقطة التي تحتاجها لمتابعة التمرير. للقيام بذلك ، سنقوم مباشرة في عملية إلغاء كل صفحة بتحديد عدد السجلات في الجدول ومقارنتها بالقيمة المتوقعة للمجموع. بمجرد أن تكون متساوية ، ننتقل إلى النهاية ويمكنك الانتقال إلى السباق التالي. قمنا أيضًا بتعيين مهلة 60 ثانية. أكلنا خلال هذا الوقت ، لا نصل إلى المجموع ، نذهب إلى السباق التالي. سيتم حفظ رمز الصفحة في ملف. سنحفظ ملفات جميع الأجناس في مجلد واحد ، ونطلق عليها اسم السباقات ، أي بالقيمة في عمود الحدث في جدول الملخص. لتجنب تضارب الأسماء ، من الضروري أن يكون لجميع الأجناس أسماء مختلفة في الجدول المحوري. افحص هذا:

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
حسنًا ، في جدول الملخص ، هناك تكرار ، علاوة على ذلك ، التواريخ وعدد المشاركين ( ذكور ، إناث ، روس ، إجمالي ) ، والروابط مختلفة. تحتاج إلى التحقق من هذه البروتوكولات ، وهناك القليل منها ، حتى تتمكن من القيام بذلك يدويًا. الآن جميع الأسماء فريدة من نوعها ، أطلقنا دورة تعدين كبيرة:



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()

هذه عملية طويلة. ولكن عندما يتم إعداد كل شيء وتبدأ هذه الآلية الثقيلة في الدوران ، وإضافة ملفات البيانات واحدة تلو الأخرى ، يأتي الشعور بالإثارة اللطيفة. يتم تحميل حوالي ثلاثة بروتوكولات فقط في الدقيقة ، ببطء شديد. غادر لتدور ليلا. استغرق حوالي 10 ساعات. في الصباح ، تم تحميل معظم البروتوكولات. كما يحدث عادة عند العمل مع شبكة ، يفشل عدد قليل. استأنفها بسرعة بمحاولة ثانية.



لذا ، لدينا 1،922 ملفًا بسعة إجمالية تبلغ 3 غيغابايت تقريبًا. رائع! لكن التعامل مع ما يقرب من 300 سباق انتهى بمهلة. ما المشكلة؟ تحقق بشكل انتقائي ، وتبين أن القيمة الإجمالية من الجدول المحوري وعدد الإدخالات في بروتوكول السباق التي قمنا بفحصها قد لا تتوافق بالفعل. هذا أمر محزن لأنه لم يتضح سبب هذا التناقض. إما أن هذا يرجع إلى حقيقة أنه لن ينتهي الجميع ، أو نوع من الأخطاء في قاعدة البيانات. بشكل عام ، الإشارة الأولى لنقص البيانات. على أي حال ، نتحقق من تلك التي يكون فيها عدد الإدخالات 100 أو 0 ، هؤلاء هم أكثر المرشحين المشبوهة. كان هناك ثمانية منهم. قم بتنزيلها مرة أخرى تحت تحكم وثيق. بالمناسبة ، يوجد اثنان منهم في الواقع 100 إدخال.

حسنًا ، لدينا جميع البيانات. ننتقل إلى التحليل. مرة أخرى ، في دورة سنجري كل سباق ، ونقرأ الملف ونحفظ المحتويات في DataFrame من الباندا . سنقوم بدمج إطارات البيانات هذه في إملاء ، حيث تكون أسماء الأجناس هي المفاتيح - أي أن قيم الأحداث من الجدول المحوري أو أسماء الملفات مع كود html لصفحات السباق ، تتطابق.

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
بالإضافة إلى الجدول الذي يتضمن نتائج المشاركين ، يحتوي ملف html لكل سباق أيضًا على تاريخ المسابقة واسمها ومكانها. التاريخ والاسم موجودان بالفعل في الجدول المحوري ، ولكن لا يوجد موقع. نقرأ هذه المعلومات من ملفات html ونضيفها إلى عمود جديد في الجدول المحوري.

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...
حفظ. إلى ملف جديد.

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

الجزء 2. اكتب الصب والتنسيق


لذا ، قمنا بتنزيل جميع البيانات ووضعناها في إطارات البيانات. ومع ذلك ، فإن جميع القيم من نوع str . ينطبق هذا على التاريخ ، وعلى النتائج ، والموقع ، وجميع المعلمات الأخرى. يجب تحويل جميع المعلمات إلى الأنواع المناسبة.

لنبدأ بالجدول المحوري.
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...
...........................

التاريخ و الوقت


الحدث ، في الموضع و صلة ستترك كما هي. تحويل التاريخ إلى تاريخ الباندا كما يلي:

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

الباقي يلقي إلى نوع صحيح:

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

كل شيء سار بسلاسة ، لم تنشأ أخطاء. لذلك كل شيء على ما يرام - احفظ:

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

الآن سباقات الإطارات. نظرًا لأن جميع السباقات أكثر ملاءمة وأسرع في المعالجة في وقت واحد ، وليست واحدة في كل مرة ، فسوف نجمعها في إطار بيانات ar واحد كبير (قصير لجميع السجلات ) باستخدام طريقة concat .

ar = pd.concat(rd) 

يحتوي ar على 1،416،365 إدخالات.

الآن قم بتحويل المكان والمكان في المجموعة إلى قيمة صحيحة.

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

بعد ذلك ، نعالج الأعمدة بقيم مؤقتة. سنلقيها في النوع Timedelta من الباندا . ولكن لكي ينجح التحويل ، تحتاج إلى إعداد البيانات بشكل صحيح. يمكنك أن ترى أن بعض القيم التي تستغرق أقل من ساعة تذهب دون تحديد النصيحة ذاتها. تحتاج لإضافته.
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]

الآن ، لا تزال السلاسل المتبقية ، تبدو كما يلي: التحويل إلى Timedelta :
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])

أرضية


استمر. تحقق من وجود قيم M و F فقط في عمود الجنس :

ar['sex'].unique() 

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

في الواقع ، لا تزال هناك سلسلة فارغة ، أي لم يتم تحديد الجنس. دعونا نرى كم عدد هذه الحالات:

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

Out: 2538

ليس الكثير جيد. في المستقبل ، سنحاول تقليل هذه القيمة بشكل أكبر. في هذه الأثناء ، اترك عمود الجنس كما هو في شكل خطوط. سنحفظ النتيجة قبل الانتقال إلى تحولات أكثر خطورة وخطورة. من أجل الحفاظ على الاستمرارية بين الملفات ، نقوم بتحويل إطار البيانات المدمج ar إلى قاموس إطارات البيانات 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)

بالمناسبة ، بسبب تحويل أنواع بعض الأعمدة ، انخفضت أحجام الملفات من 367 كيلوبايت إلى 295 كيلوبايت للجدول المحوري ومن 251 ميجابايت إلى 168 ميجابايت لبروتوكولات السباق.

الرقم الدولي


الآن دعنا نرى البلد.

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 قيمة فريدة.

بشكل أساسي ، يشار إلى البلد برمز إلكتروني مكون من ثلاثة أرقام في حالة الأحرف الكبيرة. لكن على ما يبدو ، ليس دائمًا. في الواقع ، هناك معيار دولي ISO 3166 ، حيث يتم تحديد الرموز المكونة من ثلاثة أرقام والرقمين لجميع البلدان ، بما في ذلك حتى تلك التي لم تعد موجودة. بالنسبة إلى python ، يمكن العثور على أحد تطبيقات هذا المعيار في حزمة pycountry . وإليك كيف يعمل:

import pycountry as pyco

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

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

وبالتالي ، سوف نتحقق من جميع الرموز المكونة من ثلاثة أرقام ، مما يؤدي إلى الأحرف الكبيرة ، والتي تعطي استجابة في countries.get (...) و historical_countries.get (...) :

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])

كان هناك 190 من أصل 412 منهم ، أي أقل من النصف.
بالنسبة لـ 222 المتبقية (نشير إلى قائمتهم بواسطة tofix ) ، سننشئ قاموس مطابقة الإصلاح ، حيث سيكون المفتاح هو الاسم الأصلي ، وتكون القيمة هي رمز مكون من ثلاثة أرقام وفقًا لمعيار ISO.

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

أولاً ، تحقق من الرموز المكونة من رقمين باستخدام pycountry.countries.get (alpha_2 = ...) ، مما يؤدي إلى الأحرف الكبيرة:

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

ثم الأسماء الكاملة من خلال pycountry.countries.get (name = ...) ، pycountry.countries.get (common_name = ...) ، مما يؤدي إلى النموذج 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

وبالتالي ، قمنا بتقليل عدد القيم غير المعترف بها إلى 190. لا يزال كثيرًا جدًا: قد تلاحظ أنه لا يزال هناك العديد من الرموز المكونة من ثلاثة أرقام ، ولكن هذا ليس ISO. ماذا بعد؟ اتضح أن هناك معيار آخر - الأولمبية . لسوء الحظ ، لم يتم تضمين تنفيذه في pycountry وعليك البحث عن شيء آخر. تم العثور على الحل في شكل ملف csv على datahub.io . وضع محتويات هذا الملف في DataFrame الباندا يسمى قوات الدفاع المدني . ioc - اللجنة الأولمبية الدولية (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

من بين الرموز الثلاثة المكونة من tofix ، تم العثور على 82 بطاقة IOC مقابلة. أضفهم إلى قاموسنا المطابق.

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 القيم الخام المتبقية. يتم إنهاءها يدويًا ، وأحيانًا تلجأ إلى Google للمساعدة. ولكن حتى التحكم اليدوي لا يحل المشكلة بالكامل. لا تزال هناك 49 قيمة من المستحيل بالفعل تفسيرها. ربما تكون معظم هذه القيم مجرد أخطاء في البيانات.
{'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']

ستحتوي هذه المفاتيح على سلسلة فارغة في القاموس المطابق.

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

أخيرًا ، نضيف إلى رموز القاموس المتطابقة الصالحة ولكنها مكتوبة بأحرف صغيرة.

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

حان الوقت الآن لتطبيق البدائل الموجودة. لحفظ البيانات الأولية لمزيد من المقارنة، نسخ البلاد العمود إلى خام البلاد . ثم ، باستخدام القاموس المطابق الذي تم إنشاؤه ، نقوم بتصحيح القيم في عمود البلد التي لا تتوافق مع ISO.

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

هنا ، بالطبع ، لا يمكن للمرء الاستغناء عن الرسم البياني ، الجدول يحتوي على مليون ونصف صف تقريبًا. ولكن حسب القاموس نقوم بدورة ، ولكن كيف؟ تحقق من عدد السجلات التي تم تغييرها:

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

Out: 315955

أي أكثر من 20٪ من الإجمالي.

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

هذا هو عدد السجلات بدون دولة أو مع دولة غير رسمية. انخفض عدد البلدان الفريدة من 412 إلى 250. ها هي: الآن لا توجد انحرافات. نحفظ النتيجة في ملف details2.pkl جديد ، بعد تحويل إطار البيانات المدمج مرة أخرى إلى قاموس لإطارات البيانات ، كما حدث سابقًا.
['', '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']



موقعك


تذكر الآن أن ذكر البلدان موجود أيضًا في الجدول المحوري في العمود loc . كما أنه يحتاج إلى تقديم نظرة قياسية. فيما يلي قصة مختلفة قليلاً: لا يمكن رؤية رموز ISO أو الأولمبية. يتم وصف كل شيء في شكل حر إلى حد ما. يتم سرد المدينة والبلد والمكونات الأخرى للعنوان باستخدام فاصلة ، وترتيب عشوائي. في مكان ما في المقام الأول ، في مكان ما في الأخير. لن يساعد pycountry هنا. وهناك الكثير من السجلات - لسباق 1922 525 موقعًا فريدًا (في شكله الأصلي). ولكن هنا تم العثور على أداة مناسبة. هذا هو geopy ، وهي geolocator Nominatim . يعمل مثل هذا:
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))

عند الطلب ، في شكل عشوائي ، يعطي إجابة منظمة - العنوان والإحداثيات. إذا قمت بتعيين اللغة ، كما هو الحال هنا - الإنجليزية ، فإن ما يمكنها - ستترجم. بادئ ذي بدء ، نحتاج إلى الاسم القياسي للدولة للترجمة اللاحقة إلى رمز ISO. يأخذ فقط المكان الأخير في خاصية العنوان . نظرًا لأن الموقع الجغرافي يرسل طلبًا إلى الخادم في كل مرة ، فإن هذه العملية ليست سريعة وتستغرق 500 دقيقة لـ 500 سجل. علاوة على ذلك ، يحدث أن الجواب لا يأتي. في هذه الحالة ، يساعد الطلب الثاني أحيانًا. في ردي الأول لم يصل إلى 130 طلبًا. تم معالجة معظمها بمحاولتين. ومع ذلك ، لم تتم معالجة 34 اسمًا حتى من خلال عدة محاولات أخرى. ها هم:
['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']

يمكن ملاحظة أنه في كثير من البلدان هناك ذكر مزدوج للبلد ، وهذا يتدخل في الواقع. بشكل عام ، كان علي معالجة هذه الأسماء المتبقية وتم الحصول على العناوين القياسية للجميع. علاوة على ذلك ، من هذه العناوين ، اخترت دولة وكتبت هذه الدولة في عمود جديد في الجدول المحوري. نظرًا لأن ، كما قلت ، فإن العمل مع geopy ليس سريعًا ، فقد قررت على الفور حفظ إحداثيات الموقع - خط العرض وخط الطول. سيكونون في متناول اليد لاحقًا للتصور على الخريطة. بعد ذلك ، باستخدام pyco.countries.get (name = '...') ، بحث Alpha_3 عن البلد بالاسم وخصص رمزًا مكونًا من ثلاثة أرقام.
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...

مسافة


إجراء آخر مهم يجب القيام به على الطاولة المحورية هو تحديد المسافة لكل سباق. هذا مفيد لنا لحساب السرعات في المستقبل. في الترياتلون ، هناك أربع مسافات رئيسية - العدو ، والأوليمبية ، وشبه الحديد والحديد. يمكنك أن ترى أنه في أسماء السباقات عادة ما يكون هناك مؤشر على المسافة - هذه هي Sprint ، Olympic ، Half ، Full Words . بالإضافة إلى ذلك ، لدى المنظمين المختلفين تعييناتهم الخاصة للمسافات. نصف الرجل الحديدي ، على سبيل المثال ، تم تعيينه على أنه 70.3 - من خلال عدد الأميال في المسافة ، والأوليمبية - 5150 بعدد الكيلومترات (51.5) ، ويمكن تعيين الحديد على أنه كاملأو بشكل عام ، لعدم وجود تفسير - على سبيل المثال ، Ironman Arizona 2019 . الرجل الحديدي - إنه حديد! في التحدي ، يتم تعيين المسافة الحديدية على أنها طويلة ، والمسافة شبه الحديدية على أنها متوسطة . لدينا IronStar الروسي يعني كامل 226 ، ونصف 113 - من خلال عدد الكيلومترات ، ولكن عادة ما تكون الكلمات Full and Half موجودة أيضًا. الآن قم بتطبيق كل هذه المعرفة وقم بتمييز جميع السباقات وفقًا للكلمات الرئيسية الموجودة في الأسماء.

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]) 

في rsd ، تبين أنه تم تسجيل 1 925 سجلًا ، أي ثلاثة أكثر من إجمالي عدد السباقات ، لذلك سقط بعضها تحت معيارين. دعونا نلقي نظرة عليها:

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...
بالتاكيد هو. في الزوج الأول باسم Temiradam 113 Half 2019 ، هناك ذكر للنصف و 113 . لكن هذا ليس تناقضًا ، فقد تم تحديدهما على أنه نصفين. التالي هو Triway Olympic Sprint 2019 . يمكنك الحصول على حقا الخلط هنا - هناك الاولمبية و سبرينت . يمكنك معرفة ذلك من خلال النظر إلى البروتوكول مع نتائج السباق. أفضل وقت هو 1:09. لذلك هذا هو العدو. حذف هذا الإدخال من القائمة الأولمبية.
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)

سنفعل نفس الشيء مع تقاطع Ironman Dun Laoghaire Full Swim 70.3 2019 إليك أفضل وقت 4:00. هذا نموذجي للنصف. حذف السجل مع فهرس 85 من الامتلاء .
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)

الآن سنكتب معلومات المسافة في إطار البيانات الرئيسي ونرى ما حدث:

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
تحقق من عدم وجود إدخالات غير مغطاة:

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

Out: 0

وتحقق من مشاكلنا الغامضة:

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

eventdist
38Temiradam 113 Half 2019half
65Triway Olympic Sprint 2019sprint
82Ironman Dun Laoghaire Full Swim 70.3 2019half
كل شيء على ما يرام. حفظ في ملف جديد:

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

الفئات العمرية


الآن نعود إلى بروتوكولات السباق.

لقد قمنا بالفعل بتحليل جنس المشاركين وبلدهم ونتائجهم ، وأدخلناهم إلى نموذج قياسي. لكن بقي عمودين آخرين - المجموعة ، وفي الواقع ، الاسم نفسه. لنبدأ بالمجموعات. في الترياتلون ، من المعتاد تقسيم المشاركين حسب الفئات العمرية. غالباً ما تبرز مجموعة من المحترفين. في الواقع ، تكون الإزاحة في كل مجموعة على حدة - يتم منح الأماكن الثلاثة الأولى في كل مجموعة. في المجموعات ، يتم اختيار التأهيل للبطولات ، على سبيل المثال ، على Konu.

قم بدمج جميع السجلات ومعرفة المجموعات الموجودة بشكل عام.

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

اتضح أن هناك عددًا هائلاً من المجموعات - 581. تبدو مائة مختارة عشوائيًا على النحو التالي: لنرى أيها أكثر عددًا:
['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

يمكنك أن ترى أن هذه المجموعات هي خمس سنوات ، بشكل منفصل للرجال وبشكل منفصل للنساء ، وكذلك المجموعات المهنية MPRO و FPRO .

لذا سيكون معيارنا:

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

تغطي هذه المجموعة ما يقرب من 95٪ من جميع اللمسات النهائية.

بالطبع ، لن نتمكن من جلب جميع المجموعات إلى هذا المعيار. لكننا نبحث عن تلك التي تشبههم ونعطي جزءًا على الأقل. أولاً ، سنقوم بإحضار الأحرف الكبيرة وإزالة المسافات. إليك ما حدث: قم بتحويلها إلى معاييرنا القياسية.
['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'}

الآن نقوم بتطبيق تحويلنا على إطار البيانات الرئيسي ar ، ولكن أولاً نحفظ قيم المجموعة الأصلية في العمود الخام الجديد للمجموعة .

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

في عمود المجموعة ، نترك فقط تلك القيم التي تتوافق مع معاييرنا.

الآن يمكننا أن نقدر جهودنا:

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

Out: 273

فقط قليلا على مستوى مليون ونصف. لكنك لن تعرف حتى تحاول.

الشكل 10 المحدد على النحو التالي : احفظ الإصدار الجديد من إطار البيانات ، بعد تحويله مرة أخرى إلى القاموس rd .
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'))

اسم


الآن دعنا نعتني بالأسماء. دعونا نرى بشكل انتقائي 100 اسم من أعراق مختلفة:

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']

انها معقدة. هناك مجموعة متنوعة من الخيارات للإدخالات: الاسم الأول اسم العائلة ، الاسم الأخير الاسم الأول ، الاسم الأخير ، الاسم الأول ، الاسم الأخير ، الاسم الأول ، إلخ. أي ترتيب مختلف ، تسجيل مختلف ، في مكان ما يوجد فاصل - فاصلة. هناك أيضًا العديد من البروتوكولات التي يذهب فيها السيريلية. لا يوجد أيضًا تجانس ، ويمكن العثور على هذه التنسيقات: "اسم العائلة الاسم الأول" ، "الاسم الأول اسم العائلة" ، "الاسم الأول الاسم الأوسط الاسم الأخير" ، "الاسم الأخير الاسم الأول الاسم الأوسط". على الرغم من أنه في الواقع ، تم العثور على الاسم الأوسط أيضًا في الهجاء اللاتيني. وهنا ، بالمناسبة ، تنشأ مشكلة أخرى - الترجمة الصوتية. وتجدر الإشارة أيضًا إلى أنه حتى في حالة عدم وجود اسم وسط ، فقد لا يقتصر السجل على كلمتين. على سبيل المثال ، بالنسبة إلى اللاتينيين ، يتكون الاسم بالإضافة إلى اللقب عادة من ثلاث أو أربع كلمات. يمتلك الهولنديون البادئة فان والصينيون والكوريون لديهم أسماء مركبة تتكون عادة من ثلاث كلمات. بشكل عام ، تحتاج إلى الكشف عن هذا التوبيخ بالكامل وتوحيده إلى أقصى حد. كقاعدة عامة ، في تنسيق واحد ، يكون تنسيق الاسم هو نفسه للجميع ، ولكن حتى هنا توجد أخطاء لن نتعامل معها. لنبدأ بتخزين القيم الموجودة في اسم العمود الجديد الخام :

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

الغالبية العظمى من البروتوكولات مكتوبة باللاتينية ، لذا فإن أول شيء أود القيام به هو التحويل الصوتي. دعونا نرى ما هي الأحرف التي يمكن تضمينها في اسم المشارك.

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', '', '°', '±', 'µ', '¶', '·', '»', '', 'І', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'є', 'і', 'ў', '–', '—', '‘', '’', '‚', '“', '”', '„', '†', '‡', '…', '‰', '›', '']

ما هو موجود فقط! بالإضافة إلى الأحرف والمسافات الفعلية ، لا تزال هناك مجموعة من الشخصيات الغريبة المختلفة. من هذه ، يمكن اعتبار الفترة "." ، والواصلة "-" والفاصلة العليا "" صالحة ، وهذا غير موجود عن طريق الخطأ. بالإضافة إلى ذلك ، لوحظ أنه في العديد من الأسماء والألقاب الألمانية والنرويجية توجد علامة استفهام "؟". يبدو أنهم يستبدلون الحروف من الأبجدية اللاتينية الموسعة - "؟" ، "أ" ، "س" ، "ش" ،؟ وغيرها: فيما يلي أمثلة: الفاصلة ، على الرغم من أنها تحدث في كثير من الأحيان ، هي مجرد فاصل تم اعتماده في أعراق معينة ، لذلك ستقع أيضًا في فئة غير مقبولة. يجب ألا تظهر الأرقام في الأسماء أيضًا.
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', '', '°', '±', '¶', '·', '»', '–', '—', '‘', '’', '‚', '“', '”', '„', '†', '‡', '…', '‰', '›', '']

سنقوم بإزالة جميع هذه الأحرف مؤقتًا لمعرفة عدد الإدخالات الموجودة:

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

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

هناك 2184 مثل هذه السجلات ، أي 0.15 ٪ فقط من العدد الإجمالي - قليل جدًا. دعونا نلقي نظرة على 100 منهم:

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']

ونتيجة لذلك ، بعد الكثير من البحث ، تقرر: استبدال جميع الأحرف الأبجدية ، بالإضافة إلى مسافة ، واصلة ، فاصلة عليا وعلامة استفهام ، بفاصلة ، نقطة ورمز \ xa0 ومسافات ، واستبدال جميع الأحرف الأخرى بسلسلة فارغة ، أي حذف فقط.

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, '')

ثم تخلص من المساحات الإضافية:

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

دعونا نرى ما حدث:

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
ولوحظ أيضًا أن هناك أسماء تتكون بالكامل من علامات الاستفهام.

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

يوجد 3429 منهم ، ويشبه ذلك: هدفنا من جعل الأسماء على نفس المستوى هو جعل نفس الأسماء تبدو متشابهة ، ولكن مختلفة بطرق مختلفة. في حالة الأسماء التي تتكون من علامات استفهام فقط ، فإنها تختلف فقط في عدد الأحرف ، ولكن هذا لا يعطي الثقة الكاملة في أن الأسماء التي تحمل نفس الرقم هي نفسها حقًا. لذلك ، نستبدلهم جميعًا بسلسلة فارغة ولن يتم النظر فيها في المستقبل.
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'] = ''

العدد الإجمالي للإدخالات حيث يكون الاسم هو السلسلة الفارغة 3454. ليس كثيرًا - سننجو. الآن بعد أن تخلصنا من الشخصيات غير الضرورية ، يمكننا المضي قدمًا في التحويل الصوتي. للقيام بذلك ، قم أولاً بإحضار كل شيء إلى أحرف صغيرة حتى لا تقوم بعمل مزدوج.

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

بعد ذلك ، قم بإنشاء قاموس:

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'}

كما تضمن أيضًا رسائل من ما يسمى الأبجدية السيريلية الممتدة - "є" و "і" و "ў" ، والتي تُستخدم باللغتين البيلاروسية والأوكرانية ، بالإضافة إلى الحرف اليوناني "µ" . تطبيق التحول:

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

الآن ، من الأحرف الصغيرة العاملة ، سنترجم كل شيء إلى التنسيق المألوف ، حيث يبدأ الاسم الأول والأخير بحرف كبير:

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

دعونا نرى ما حدث.

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
أخيرًا ، تحقق من الأحرف الفريدة:

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']

كله صحيح. ونتيجة لذلك ، أثرت التصحيحات على 1،253،882 أو 89 ٪ من السجلات ، وانخفض عدد الأسماء الفريدة من 660،207 إلى 599،186 ، أي بنسبة 61 ألفًا أو ما يقرب من 10 ٪. رائع! حفظ إلى ملف جديد ، بعد ترجمة اتحاد سجلات ar مرة أخرى إلى قاموس بروتوكول rd .

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

الآن نحن بحاجة إلى استعادة النظام. أي أن جميع السجلات ستبدو - الاسم الأول اسم العائلة أو اسم العائلة الاسم الأول . أيهما سيتم تحديده. صحيح ، بالإضافة إلى الاسم واللقب ، تحتوي بعض البروتوكولات أيضًا على الأسماء الوسطى. وقد يحدث أن يتم كتابة نفس الشخص بشكل مختلف في بروتوكولات مختلفة - في مكان ما باسم وسط ، في مكان ما بدون. سيتداخل ذلك مع هويته ، لذا حاول إزالة الاسم الأوسط. عادة ما يكون للرعاية الأبوية للرجال نهاية "hiv" ، وبالنسبة للنساء - "vna" . لكن هناك استثناءات. على سبيل المثال - Ilyich ، Ilyinichna ، Nikitich ، Nikitichna. صحيح أن هناك استثناءات قليلة جدًا. كما ذكرنا سابقًا ، يمكن اعتبار تنسيق الأسماء في بروتوكول واحد دائمًا. لذلك ، للتخلص من علم الأبناء ، تحتاج إلى العثور على السباق الذي هم فيه. للقيام بذلك ، ابحث عن إجمالي عدد الأجزاء "vich" و "vna" في اسم العمودومقارنتها بالعدد الإجمالي للإدخالات في كل بروتوكول. إذا كانت هذه الأرقام قريبة ، فهناك اسم الأوسط ، وإلا لا. من غير المعقول أن نبحث عن الامتثال الصارم ، حتى في السباقات حيث يتم تسجيل الأسماء الوسطى ، على سبيل المثال ، يمكن للأجانب المشاركة ، وسيتم تسجيلهم بدونه. يحدث أيضًا أن المشارك نسيت أو لم يرغب في الإشارة إلى اسمه الأوسط. من ناحية أخرى ، هناك أيضًا ألقاب تنتهي بـ "vich" ، وهناك الكثير منها في بيلاروسيا وبلدان أخرى بلغات المجموعة السلافية. بالإضافة إلى ذلك ، قمنا بعمل تحويل صوتي. كان من الممكن إجراء هذا التحليل قبل التحويل الصوتي ، ولكن بعد ذلك هناك فرصة لتفويت بروتوكول فيه أسماء متوسطة ، ولكن في البداية كان بالفعل في اللاتينية. لذلك كل شيء على ما يرام.

لذا ، سنبحث عن جميع البروتوكولات التي يوجد فيها عدد الأجزاء "vich" و "vna" في العمودالاسم هو أكثر من 50٪ من إجمالي عدد الإدخالات في البروتوكول.

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]

يوجد 29 بروتوكولًا ، أحدها هو: ومن المثير للاهتمام أنه إذا أخذنا 20٪ أو العكس بالعكس 70٪ ، فلن تتغير النتيجة ، سيظل هناك 29 ، لذا قمنا بالاختيار الصحيح. وفقًا لذلك ، أقل من 20٪ - تأثير الألقاب ، أكثر من 70٪ - تأثير السجلات الفردية بدون أسماء وسط. بعد فحص البلاد بمساعدة طاولة محورية ، اتضح أن 25 منهم كانوا في روسيا ، و 4 في أبخازيا. الانتقال. سنقوم فقط بمعالجة السجلات التي تحتوي على ثلاثة مكونات ، وهي تلك التي يوجد فيها (من المفترض) اللقب والاسم والاسم الأوسط.
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

غالبية هذه السجلات هي 86٪. الآن تلك التي تنقسم فيها المكونات الثلاثة إلى أعمدة name0 ، name1 ، name2 :

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]

إليك ما يبدو عليه أحد البروتوكولات: هنا ، على وجه الخصوص ، من الواضح أن تسجيل المكونين لم تتم معالجته. الآن ، لكل بروتوكول ، تحتاج إلى تحديد العمود الذي له اسم الأوسط. هناك خياران فقط - name1 ، name2 ، لأنه لا يمكن أن يكون في المقام الأول. بمجرد تحديد ذلك ، سنقوم بجمع اسم جديد بالفعل بدونه.
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
الآن، تعيين بعناية الاسم الجديد للعمود الرئيسي اسم ، حيث أنها ليست فارغة، وحذف الأعمدة المساعدة.

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...
هذا كل شيء. قمنا بتحرير 2035 إدخالات. ليس سيئا. تم الحفظ.

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

تحتاج الآن إلى إحضار الأسماء إلى نفس الترتيب. أي أنه من الضروري في جميع البروتوكولات أن يكون الاسم متبوعًا أولاً بالاسم الأخير ، أو العكس بالعكس - أولاً الاسم الأخير ، ثم الاسم الأول ، أيضًا في جميع البروتوكولات. يعتمد على المزيد ، الآن سنكتشف. الوضع معقد قليلاً بسبب حقيقة أن الاسم الكامل يمكن أن يتكون من أكثر من كلمتين ، حتى بعد إزالة الاسم الأوسط.

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)

عدد الكلمات في الاسم عدد السجلات حصة السجلات (٪) بالطبع ، الغالبية العظمى (91٪) كلمتان - مجرد اسم ولقب. لكن الإدخالات التي تحتوي على ثلاث وأربع كلمات هي أيضًا كثيرة جدًا. دعونا نلقي نظرة على جنسية مثل هذه السجلات:
(%)
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

حسنًا ، في المقام الأول إسبانيا ، في المرتبة الثانية - المكسيك ، بلد من أصل إسباني ، أبعد من الولايات المتحدة ، حيث يوجد أيضًا تاريخًا الكثير من اللاتينيين. البرازيل والفلبين أيضا أسماء الإسبانية (والبرتغالية). الدنمارك وهولندا وألمانيا وجنوب إفريقيا وإيطاليا وبلجيكا وفرنسا هي مسألة أخرى ، فهناك ببساطة نوع من البادئة لللقب ، وبالتالي هناك أكثر من كلمتين. في جميع هذه الحالات ، ومع ذلك ، عادة ما يتكون الاسم نفسه من كلمة واحدة ، والاسم الأخير لكلمتين ، ثلاثة. بالطبع ، هناك استثناءات لهذه القاعدة ، لكننا لن نعالجها بعد الآن. أولاً ، لكل بروتوكول ، تحتاج إلى تحديد نوع النظام: اسم اللقب أو العكس. كيف افعلها؟ حدثت لي الفكرة التالية: أولاً ، عادةً ما يكون تنوع الألقاب أكبر بكثير من تنوع الأسماء. يجب أن يكون الأمر كذلك حتى في إطار بروتوكول واحد. ثانيا،عادة ما يكون طول الاسم أقل من طول اللقب (حتى بالنسبة للألقاب غير المركبة). سنستخدم مجموعة من هذه المعايير لتحديد الترتيب الأولي.

حدد الكلمات الأولى والأخيرة بالاسم الكامل:

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

قم بتحويل إطار بيانات ar المدمج مرة أخرى إلى القاموس rd بحيث تقع الأعمدة الجديدة nwin، ns0، ns في إطار البيانات لكل سباق. بعد ذلك ، نحدد عدد البروتوكولات بالترتيب " First Name Last Name" وعدد البروتوكولات بالترتيب العكسي وفقًا لمعيارنا. سننظر فقط في الإدخالات حيث يتكون الاسم الكامل من كلمتين. في نفس الوقت ، احفظ الاسم (الاسم الأول) في عمود جديد:

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']

اتضح ما يلي: ترتيب الاسم الأول اسم العائلة - 244 بروتوكول ، ترتيب الاسم الأخير أولاً - 1508 بروتوكول.

وفقًا لذلك ، سنؤدي إلى التنسيق الأكثر شيوعًا. اتضح أن المبلغ أقل من المبلغ الإجمالي ، لأننا تحققنا من استيفاء معيارين في نفس الوقت ، وحتى مع عدم المساواة الصارمة. هناك بروتوكولات يتم فيها تحقيق معيار واحد فقط ، أو أنه ممكن ، ولكن من غير المحتمل أن تحدث المساواة. ولكن هذا غير مهم على الإطلاق حيث يتم تعريف التنسيق.

الآن ، بافتراض أننا حددنا الطلب بدقة عالية بما فيه الكفاية ، مع عدم نسيان أنه ليس دقيقًا بنسبة 100٪ ، سنستخدم هذه المعلومات. اعثر على الأسماء الأكثر شهرة من عمود الاسم الأول :

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

خذ أولئك الذين التقوا أكثر من مائة مرة:

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

كان هناك 1673 منهم ، وإليك المئات منها مرتبة بترتيب تنازلي للشعبية: الآن ، وباستخدام هذه القائمة ، سنجري جميع البروتوكولات ونقارن حيث يوجد المزيد من التطابقات - في الكلمة الأولى من الاسم أو في الأخيرة. سننظر فقط في الأسماء المكونة من كلمتين. إذا كان هناك المزيد من التطابقات مع الكلمة الأخيرة ، فإن الترتيب صحيح ، إذا كان مع الكلمة الأولى ، فهذا يعني العكس. علاوة على ذلك ، نحن هنا أكثر ثقة بالفعل ، لذا يمكنك استخدام هذه المعرفة ، وسنضيف قائمة بأسماء بروتوكولهم التالي إلى القائمة الأولية للأسماء الشائعة مع كل تمريرة. نقوم بفرز البروتوكولات مسبقًا حسب تكرار ظهور الأسماء من القائمة الأولية من أجل تجنب الأخطاء العشوائية وإعداد قائمة أكثر شمولاً لتلك البروتوكولات التي يوجد بها عدد قليل من التطابقات والتي سيتم معالجتها بالقرب من نهاية الدورة.
['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'])))

كان هناك 235 بروتوكولات. وهذا هو ، تقريبًا نفس ما حدث في التقريب الأول (244). للتأكد ، نظرت بشكل انتقائي في السجلات الثلاثة الأولى من كل منها ، وتأكدت من أن كل شيء كان صحيحًا. تحقق أيضًا من أن المرحلة الأولى من التصنيف أعطت 36 إدخالًا كاذبًا من اسم اسم الفصل الدراسي واثنين خطأ من اسم اسم الفصل الدراسي . نظرت إلى السجلات الثلاثة الأولى من كل منها ، والواقع أن المرحلة الثانية عملت بشكل مثالي. الآن ، في الواقع ، يبقى إصلاح تلك البروتوكولات حيث تم العثور على ترتيب خاطئ:

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]

هنا في الانقسام ، حددنا عدد القطع باستخدام المعلمة n . المنطق هو: الاسم كلمة واحدة ، الأولى في الاسم الكامل. كل شيء آخر هو لقب (قد يتكون من عدة كلمات). فقط قم بتبديلها.

الآن نتخلص من الأعمدة غير الضرورية ونوفر:

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'))

تحقق من النتيجة. عشرات السجلات الثابتة العشوائية: تم إصلاح ما مجموعه 108 ألف سجل. وانخفض عدد الأسماء الكاملة الفريدة من 598 إلى 547 ألفًا. غرامة! مع الانتهاء من التنسيق.
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


الجزء 3. استعادة البيانات غير مكتملة


انتقل الآن إلى استعادة البيانات المفقودة. وهناك مثل هذا.

بلد


لنبدأ بالبلد. البحث عن جميع السجلات التي لم يُشر فيها البلد:

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

منهم 3221 منهم 10 عشوائية:
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

عدد الأسماء الفريدة بين السجلات بدون بلد هو 0551. لنرى ما إذا كان يمكن تقليل هذا الرقم.

والحقيقة هي أنه في نادٍ ما يقتصر الأشخاص في الترياثلون على سباق واحد فقط ، وعادة ما يشاركون في المسابقات بشكل دوري ، عدة مرات في الموسم ، من سنة إلى أخرى ، يتدربون باستمرار. لذلك ، بالنسبة للعديد من الأسماء في البيانات ، هناك على الأرجح أكثر من سجل واحد. لاستعادة معلومات حول البلد ، حاول العثور على سجلات بنفس الاسم من بين تلك التي يشار إليها البلد.

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', …]

كان هناك 2236 منهم ، أي ما يقرب من ثلاثة أرباع. الآن ، لكل اسم من هذه القائمة ، تحتاج إلى تحديد البلد من خلال السجلات التي توجد فيها. ولكن يحدث أن الاسم نفسه موجود في العديد من السجلات وفي بلدان مختلفة. هذا إما يحمل الاسم نفسه ، أو ربما انتقل الشخص. لذلك ، نقوم أولاً بمعالجة تلك التي يكون فيها كل شيء فريدًا.

fix = {}

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

صنع في حلقة. ولكن ، بصراحة ، يعمل لمدة طويلة - حوالي ثلاث دقائق. إذا كان هناك ترتيب بمزيد من الإدخالات ، فربما يتعين عليك التوصل إلى تنفيذ متجه. كان هناك 2،013 إدخال ، أو 90 ٪ من الإمكانات.

الأسماء التي قد تظهر بلدان مختلفة في سجلات مختلفة ، تأخذ البلد الذي يحدث في أغلب الأحيان.

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]

وبالتالي ، تم العثور على تطابقات لـ 2،208 اسمًا ، أو 99٪ من جميع الأسماء المحتملة. نطبق هذه المراسلات:
{'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
بعد تصحيحاتنا ، انخفض عدد السجلات بدون دولة إلى 909 ، أي أكثر من ثلاث مرات. على الرغم من أن العدد الإجمالي لـ 2،208 ليس كبيرًا جدًا على خلفية مليون ونصف ، إلا أنه لا يزال لطيفًا.

وعلاوة على ذلك، كالعادة، نترجم جنبا إلى جنب إطار بيانات ع مرة أخرى في الثالثة القاموس وحفظه.

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

أرضية


كما هو الحال في البلدان ، هناك سجلات لا يشار فيها إلى جنس المشارك.

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

يوجد 2538 منهم ، قليل نسبياً ، ولكن مرة أخرى سنحاول أن نجعل أقل. احفظ القيم الأصلية في عمود جديد.

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

على عكس البلدان التي استرجعنا فيها معلومات بالاسم من البروتوكولات الأخرى ، كل شيء أكثر تعقيدًا هنا. والحقيقة هي أن البيانات مليئة بالأخطاء وهناك العديد من الأسماء (مجموع 2101) التي تم العثور عليها بعلامات لكلا الجنسين.

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
نعم ، من حيث المبدأ ، هناك أسماء للجنسين (أو مخنثين) ، أي تلك المستخدمة لتسمية كل من الأولاد والبنات. بالنسبة للرياضيين الآسيويين ، يصعب تحديد الجنس بالاسم - ربما ليس لدي ما يكفي من المعرفة. ومع ذلك ، من الصعب تصديق أن اسم إيرينا أو أناستاسيا ينتمي إلى رجل ، وكان بنيامين يسمى امرأة. بالإضافة إلى ذلك ، اكتشفت في وقت ما أن هناك عددًا كبيرًا من البروتوكولات التي يتم فيها تمييز جميع المشاركين بنوع واحد.

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

يوجد 633 منهم ، يبدو أن هذا ممكن تمامًا ، مجرد بروتوكول منفصل للنساء ، بشكل منفصل للرجال. لكن الحقيقة هي أن جميع هذه البروتوكولات تقريبًا تحتوي على فئات عمرية من كلا الجنسين (تبدأ الفئات العمرية للذكور بالحرف M ، الإناث - بالحرف F ). على سبيل المثال: من المتوقع أن يبدأ اسم الفئة العمرية بالحرف M للرجال والحرف F للنساء. في المثالين السابقين ، على الرغم من الأخطاء في عمود الجنس

'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
، لا يزال يبدو أن اسم المجموعة يصف جنس العضو بشكل صحيح. استنادًا إلى العديد من أمثلة الأمثلة ، نفترض أن المجموعة مُشار إليها بشكل صحيح ، وقد يُشار إلى الجنس بشكل خاطئ. ابحث عن جميع الإدخالات حيث لا يتطابق الحرف الأول في اسم المجموعة مع الجنس. سنأخذ الاسم الأولي لمجموعة المجموعة الخام ، حيث أنه خلال التوحيد القياسي تركت العديد من السجلات بدون مجموعة ، لكننا الآن بحاجة فقط إلى الحرف الأول ، لذا فإن المعيار ليس مهمًا.

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

هناك 26 161 مثل هذه السجلات. حسنًا ، دعنا نصحح الجنس وفقًا لاسم الفئة العمرية:

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

دعونا نلقي نظرة على النتيجة: جيد. كم عدد السجلات المتبقية الآن بدون جنس؟
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'] != '')]

اتضح بالضبط واحد! حسنًا ، لم يتم تحديد المجموعة حقًا ، ولكن ، على ما يبدو ، هذه امرأة. Emily هو اسم أنثى ، إلى جانب هذه المشاركة (أو التي تحمل اسمها) التي انتهت قبل عام ، وفي هذا البروتوكول يشار إلى الجنس والمجموعة. قم بالاستعادة هنا يدويًا وانتقل.
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'

الآن جميع السجلات مع الجنس.

* بشكل عام ، بالطبع ، من الخطأ القيام بذلك - مع الجري المتكرر ، إذا تغير شيء ما في السلسلة من قبل ، على سبيل المثال ، في تحويل الاسم ، فقد يكون هناك أكثر من سجل واحد بدون جنس ، ولن يكون جميعهم من الإناث ، سيحدث خطأ. لذلك ، يجب عليك إما إدراج منطق ثقيل للبحث عن مشارك بنفس الاسم والجنس في البروتوكولات الأخرى ، مثل استعادة بلد ما ، واختباره بطريقة أو بأخرى ، أو حتى لا يعقد الأمر بشكل غير ضروري ، أضف إلى هذا المنطق التحقق من وجود سجل واحد فقط والاسم على هذا النحو ، أو خلاف ذلك ، من خلال استبعاد استثناء من شأنه إيقاف الكمبيوتر المحمول بأكمله ، يمكنك ملاحظة انحراف عن الخطة والتدخل.

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

يبدو أن هذا يمكن أن يهدأ. لكن الحقيقة هي أن التصحيحات تستند إلى افتراض أن المجموعة يشار إليها بشكل صحيح. وهو كذلك بالفعل. تقريبا دائما. تقريبا. ومع ذلك ، تم ملاحظة العديد من التناقضات عن طريق الخطأ ، لذلك الآن دعونا نحاول تحديدها جميعًا ، جيدًا أو قدر الإمكان. كما ذكرنا من قبل ، في المثال الأول ، كان على وجه التحديد حقيقة أن الجنس لا يتوافق مع الاسم على أساس أفكاره الخاصة حول أسماء الذكور والإناث التي تحرسنا.

البحث عن جميع الأسماء في سجلات الذكور والإناث. هنا ، يُفهم الاسم على أنه الاسم ، وليس الاسم الكامل ، أي بدون اسم ، ما يسمى الاسم الأول باللغة الإنجليزية .

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

يتم سرد ما مجموعه 32508 أسماء الذكور. فيما يلي أكثر 50 شركة شعبية:
['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

عدد أقل من النساء - 14 423 الأكثر شيوعًا: جيد ، يبدو أنه يبدو منطقيًا. دعونا نرى ما إذا كانت هناك تقاطعات.
['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

يوجد. وهناك 2811 منهم دعنا ننظر إليهم عن كثب. بادئ ذي بدء ، نجد عدد السجلات التي تحمل هذه الأسماء:

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

يوجد 725 562 هذا نصف! انه رائع! يوجد ما يقرب من 37000 اسم فريد ، ولكن نصف السجلات بها 2800 اسمًا. لنر ما هي هذه الأسماء ، وهي الأكثر شيوعًا. للقيام بذلك ، قم بإنشاء إطار بيانات جديد حيث ستكون هذه الأسماء مؤشرات:

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

نحن نحسب عدد السجلات من الذكور والإناث مع كل منها.

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
............
لذلك ... يبدو مريبا. على حد علمي ، كل هذه الأسماء ذكورية. ولكن مع كل واحد منهم هناك كمية صغيرة من السجلات النسائية. ربما تكون هذه أخطاء بيانات.

دعونا نلقي نظرة على أسماء الإناث.

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

totalMF
Jennifer365233649
Sarah328843284
Laura263632633
Lisa261822616
Anna2563102553
Michelle237312372
Maria25553862169
Andrea432322352088
Nicole202562019
Julie193821936
............
نفسه. تقريبا. تبرز أندريا ، وهو بالفعل اسم مخنث ، وأقل بقليل من ماريا ، لسبب ما.

في الواقع ، لا تنس أننا نبحث عن بيانات من أشخاص من دول مختلفة جدًا ، يمكن للمرء أن يقول ، حول العالم. في الثقافات المختلفة ، يمكن استخدام نفس الاسم بطرق مختلفة تمامًا. هنا مثال. كارين هي واحدة من أسماء الإناث الأكثر شعبية من قائمتنا ، ولكن من ناحية أخرى هناك اسم كارين ، والذي سيتم كتابته بالترجمة بنفس الطريقة ، ولكنه مخصص حصريًا للرجال. لحسن الحظ ، هناك حزمة تحملها كل هذه الحكمة العالمية. يطلق عليه اسم تخمين الجنس .

يعمل مثل هذا:

import gender_guesser.detector as gg

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

Out: 'male'


d.get_gender(u'Evgeniya')

Out: 'female'

كل شيء على ما يرام. ولكن إذا تحققت من اسم أندريا ، فإنه يعطي أيضًا أنثى ، وهذا ليس صحيحًا تمامًا. صحيح ، هناك مخرج. إذا نظرت إلى خاصية أسماء الكاشف ، فإن كل الغموض يصبح مرئيًا هناك.

d.names['Andrea']

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

نعم ، هذا هو ، get_gender يمنحك الخيار الأكثر احتمالًا ، ولكن في الواقع يمكن أن يكون أكثر تعقيدًا. تحقق من الأسماء الأخرى:

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 '}

أي أن قائمة أسماء كل اسم تقابل واحدًا أو أكثر من أزواج القيمة الرئيسية ، حيث المفتاح - هو الجنس: ذكر ، أنثى ، معظمهم من الذكور ، معظمهم من الإناث ، وأندي ، والقيمة - قائمة قيم البلد المقابل: 1،2،3 ... .. 9ABC . البلدان هي:

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']

لم أفهم تمامًا ما تعنيه المعاني الأبجدية الرقمية أو غيابها في القائمة على وجه التحديد. لكن هذا لم يكن مهمًا ، لأنني قررت أن أقتصر فقط على استخدام تلك الأسماء التي لها تفسير واضح. أي أنه يوجد زوج واحد فقط من القيمة الرئيسية والمفتاح إما ذكر أو أنثى . لكل اسم من dataframe لدينا ، اكتب تفسيره لمحاكي الجنس :

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'

تبين 1150 اسما. فيما يلي الأكثر شيوعًا التي تمت مناقشتها أعلاه: حسنًا ، ليس سيئًا. الآن قم بتطبيق هذا المنطق على جميع السجلات.
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)

العثور على 7 091 أسماء ذكور و 0554 أنثى. تطبيق التحول:

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'

ننظر إلى النتيجة:

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

تصحيح إدخالات 30،352 (مع التصحيح باسم المجموعة). كالعادة ، 10 عشوائية: الآن بعد أن تأكدنا من أننا حددنا الجنس بشكل صحيح ، سنقوم أيضًا بجعل المجموعات القياسية في خط. دعونا نرى أين لا تتطابق:
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 إدخالات. يستبدل الحرف الأول:

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
ربما ، في مكان ما اتضح أن التصحيحات غير صحيحة ، ولكن الجميع يعتقدون في وقت مبكر أنهم قاموا بعمل جيد أكثر من الأذى. للإحصاءات ، هذا مهم.

هذا مع استعادة الجنس. نقوم بحذف أعمدة العمل ، وترجمتها إلى القاموس وحفظها.

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

هذا كل شيء ، مع استعادة البيانات غير المكتملة.

تحديث النشرة


يبقى تحديث الجدول الموجز ببيانات محدثة حول عدد الرجال والنساء ، إلخ.

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'))

الجزء 4. أخذ العينات


الآن الترياتلون تحظى بشعبية كبيرة. خلال الموسم ، هناك العديد من المسابقات المفتوحة التي يشارك فيها عدد كبير من الرياضيين ، وخاصة الهواة. ولكنها لم تكن كذلك دائما. هناك سجلات في بياناتنا منذ عام 1990. بالتمرير عبر tristats.ru ، لاحظت أن هناك المزيد من السباقات في السنوات الأخيرة ، وعدد قليل جدًا في الأول. ولكن الآن بعد أن تم إعداد بياناتنا ، يمكنك الاطلاع عليها عن كثب.

فترة عشر سنوات


احسب عدد السباقات والانهاءات في كل عام:

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 - سباق في روسيا. روس - اللمسات الأخيرة من روسيا.

إليك ما يبدو عليه على الرسم البياني:


يمكن ملاحظة أن عدد السباقات والمشاركين في بداية الفترة وفي النهاية هو ببساطة لا يقاس. تبدأ الزيادة الكبيرة في إجمالي عدد السباقات في عام 2011 ، بينما يزداد عدد السباقات في روسيا أيضًا. علاوة على ذلك ، يمكن ملاحظة زيادة في عدد المشاركين في عام 2009. قد يشير هذا إلى زيادة الاهتمام بين المشاركين ، أي زيادة الطلب ، وبعد ذلك بعامين زاد العرض ، أي عدد مرات البدء. ومع ذلك ، لا تنس أن البيانات قد لا تكون كاملة وبعضها ، وربما العديد من السباقات مفقودة. بما في ذلك حقيقة أن مشروع جمع هذه البيانات بدأ فقط في عام 2010 ، والذي يمكن أن يفسر أيضًا قفزة كبيرة في الرسم البياني في هذه اللحظة بالذات. بما في ذلك ، لمزيد من التحليل ، قررت أن يستغرق السنوات العشر الماضية. هذه فترة طويلة إلى حد ما ،من أجل تتبع أي اتجاهات على مدى عدة سنوات ، في حين أنها قصيرة بما يكفي لعدم الوصول إلى هناك ، بشكل رئيسي المسابقات المهنية من 90s وأوائل 2000s.

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



بالمناسبة ، انخفض 84٪ من السباقات و 94٪ من الإنهاءات خلال الفترة المحددة.

يبدأ الهواة


لذا ، فإن الغالبية العظمى من المشاركين في البداية المختارة هم رياضيون هواة ، لذا يمكن الحصول على إحصائيات جيدة منهم. بصراحة ، كان هذا الأمر محل اهتمام رئيسي بالنسبة لي ، لأنني أشارك في مثل هذه البدايات ، ولكن في المستوى بعيدًا جدًا عن أبطال الألعاب الأولمبية. ومع ذلك ، من الواضح أن المسابقات المهنية جرت أيضًا في الفترة المختارة. لكي لا يتم مزج مؤشرات سباقات الهواة والمحترفين ، تقرر حذف هذا الأخير من الاعتبار. كيفية التعرف عليهم؟ بالسرعة. نحسبها. في إحدى المراحل الأولية لإعداد البيانات ، حددنا بالفعل نوع المسافة التي كانت في كل سباق - العدو ، الأولمبي ، النصف ، الحديد. لكل واحد منهم ، تم تحديد المسافة المقطوعة للمراحل بوضوح - السباحة وركوب الدراجات والجري. هذا هو 0.75 + 20 + 5 للسباق ، 1.5 + 40 + 10 للأولمبياد ، 1.9 + 90 + 21.1 للنصف و 3.8 + 180 + 42.2 للحديد. بالطبع ، في الواقع ، بالنسبة لأي نوع ، يمكن أن تختلف الأرقام الحقيقية من سباق إلى آخر مشروطًا بنسبة تصل إلى واحد بالمائة ، ولكن لا توجد معلومات حول هذا ، لذلك سنفترض أن كل شيء كان دقيقًا.

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

نحسب متوسط ​​السرعات القصوى لكل سباق. الحد الأقصى هنا يشير إلى متوسط ​​سرعة الرياضي الذي فاز بالمركز الأول.

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()



حسنًا ، يمكنك أن ترى أن معظم السرعات التي تم تجميعها في أكوام ما بين حوالي 15 كم / ساعة و 30 كم / ساعة ، ولكن هناك عدد معين من القيم "الكونية" تمامًا. فرز حسب متوسط ​​السرعة ونرى كم منهم:

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



هنا قمنا بتغيير المقياس ويمكننا تقدير النطاق بشكل أكثر دقة. بالنسبة للسرعات المتوسطة ، فهي تتراوح من حوالي 17 كم / ساعة إلى 27 كم / ساعة ، كحد أقصى - من 18 كم / ساعة إلى 32 كم / ساعة. بالإضافة إلى وجود "ذيول" بسرعات منخفضة جدًا وعالية جدًا. من المحتمل أن تتوافق السرعات المنخفضة مع المنافسات الشديدة مثل Norseman ، ويمكن أن تكون السرعات العالية في حالة السباحة الملغاة ، حيث كان هناك عداء سريع ، أو ببساطة بيانات خاطئة. نقطة أخرى مهمة هي الخطوة السلسة في منطقة 1200 على طول المحور X، وقيم أعلى لمتوسط ​​السرعة بعده. هناك يمكنك أن ترى اختلافًا أقل بكثير بين المتوسط ​​والسرعة القصوى مقارنة بالثلثي الأول من الرسم البياني. على ما يبدو ، هذه منافسة مهنية. لتمييزها بشكل أكثر وضوحًا ، نحسب نسبة السرعة القصوى إلى المتوسط. في المسابقات المهنية حيث لا يوجد أشخاص عشوائيين ولدى جميع المشاركين مستوى عالٍ جدًا من اللياقة البدنية ، يجب أن تكون هذه النسبة ضئيلة.

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



على هذا الرسم البياني ، يبرز الربع الأول بوضوح شديد: نسبة السرعة القصوى إلى المتوسط ​​صغيرة ، متوسط ​​السرعة العالية ، عدد قليل من المشاركين. هذه مسابقة مهنية. الخطوة على المنحنى الأخضر في مكان ما حول 1.2. سنترك السجلات فقط مع نسبة نسبة أكبر من 1.2 في عينتنا.

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

نقوم أيضًا بإزالة السجلات بسرعات منخفضة وعالية غير معتادة. في ما هي "الأرقام القياسية" العالمية الثلاثية لكل مسافة؟ نشر أوقات قياسية لتمرير مسافات مختلفة لعام 2019. إذا كنت تحسبها بسرعات متوسطة ، يمكنك أن ترى أنه لا يمكن أن يكون أعلى من 33 كم / ساعة حتى الأسرع. لذلك سنأخذ في الاعتبار البروتوكولات التي تكون فيها متوسط ​​السرعات أعلى ، غير صالحة ، وإزالتها من الاعتبار.

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

إليك ما تبقى:



الآن كل شيء يبدو متجانسًا تمامًا ولا يثير أسئلة. نتيجة لكل هذا التحديد ، فقدنا 777 من بروتوكولات 1922 ، أو 40٪. في الوقت نفسه ، لم ينخفض ​​إجمالي عدد اللمسات النهائية كثيرًا - فقط 13 ٪.

لذا ، هناك 1145 سباقا متبقيا مع 1،231،772 نهائي. أصبحت هذه العينة مادة لتحليلي وتصوري.

الجزء 5. التحليل والتصور


في هذا العمل ، كان التحليل والتصور الصحيح أبسط الأجزاء. رأس جبل الجليد ، والذي كان الجزء تحت الماء هو مجرد إعداد البيانات. كان التحليل ، في الواقع ، عملية حسابية بسيطة على سلسلة الباندا ، وحساب المتوسطات ، والتصفية - كل هذا يتم عن طريق أدوات الباندا الابتدائية والشفرة أعلاه مليئة بالأمثلة. في المقابل ، تم عمل التصور بشكل أساسي باستخدام matplotlib الأكثر القياسية . تستخدم مؤامرة ، بار ، فطيرة . ولكن في بعض الأماكن ، كان عليّ أن العبث بتوقيعات المحاور ، في حالة التواريخ والرسوم التوضيحية ، ولكن هذا ليس شيئًا يعتمد على وصف مفصل هنا. الشيء الوحيد الذي يستحق الحديث عنه هو عرض البيانات الجغرافية. على الأقل ليس كذلكماتبلوتليب .

البيانات الجغرافية


لكل سباق لدينا معلومات حول المكان. في البداية ، باستخدام geopy ، قمنا بحساب الإحداثيات لكل موقع. تقام العديد من السباقات سنويا في نفس المكان. أداة سهلة الاستخدام للغاية لعرض البيانات الجغرافية في الثعبان هي folium . وإليك كيف يعمل:
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)

ونحصل على خريطة تفاعلية مباشرة في كمبيوتر محمول المشتري.



الآن ، لبياناتنا. أولاً ، سنبدأ عمودًا جديدًا من مجموعة إحداثياتنا:

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

إحداثيات فريدة من احداثيات هي 291. ومواقع فريدة من نوعها في الموضع هم 324، مما يعني أن بعض الأسماء مختلفة قليلا، بينما في الوقت نفسه أنها تتوافق مع نفس النقطة. هذا ليس مخيفًا ، سننظر في التفرد من خلال الحبال . نحن نحسب عدد الأحداث التي مرت طوال الوقت في كل موقع (بإحداثيات فريدة):

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
... ...

الآن قم بإنشاء خريطة ، وإضافة علامات عليها في شكل دوائر ، والتي يعتمد نصف قطرها على عدد الأحداث التي تقام على الموقع. أضف علامات باسم الموقع إلى العلامات.

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)

منجز. يمكنك رؤية النتيجة:



تقدم المشاركين


في الواقع ، بالإضافة إلى التوجيه ، كان العمل على جدول زمني آخر غير تافه أيضًا. هذا هو أحدث رسم بياني لتقدم المشاركين. ها هي:



دعنا نحللها ، وفي نفس الوقت سأعطي كود العرض ، كمثال على استخدام matplotlib :

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()

الآن حول كيفية حساب البيانات له. أولاً ، كان عليك اختيار أسماء المشاركين الذين أنهوا على الأقل في سباقين ، وفي سنوات تقويمية مختلفة ، وفي الوقت نفسه ليسوا محترفين.

أولاً ، لكل بروتوكول ، املأ عمودًا جديدًا يسمى التاريخ ، والذي سيشير إلى تاريخ السباق. وسوف نحتاج أيضا بعد عام من هذا التاريخ، وسوف نبذل عمود العام . نظرًا لأننا سنقوم بتحليل سرعة كل رياضي بالنسبة لمتوسط ​​السرعة في السباق ، فإننا نحسب هذه السرعة على الفور في العمود الجديد vproc - السرعة كنسبة مئوية من المتوسط.

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()

إليك ما تبدو عليه البروتوكولات الآن: بعد ذلك ، اجمع جميع البروتوكولات في إطار بيانات واحد.
' 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)

لكل مشارك ، سنترك إدخال واحد فقط في كل سنة تقويمية:

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

بعد ذلك ، من جميع الأسماء الفريدة لهذه الإدخالات ، نجد تلك التي تحدث مرتين على الأقل:

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

يوجد 219،890 منهم دعنا نزيل أسماء الرياضيين المحترفين من هذه القائمة:

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

وكذلك أسماء الرياضيين الذين بدأوا في الأداء قبل عام 2010. للقيام بذلك ، قم بتحميل البيانات التي تم حفظها قبل أخذ عينات على مدى السنوات العشر الماضية. ضعهم في كائنات rsa (ملخص الأجناس كلها) و rda (تفاصيل السباق كلها).

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))

وأخيرًا ، نجد أسماء تحدث أكثر من مرة في نفس اليوم. وبالتالي ، فإننا نقلل من وجود الاسماء الكاملة في عينتنا.

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

إذن هناك 198،075 اسم متبقي. من مجموعة البيانات بأكملها ، نختار فقط السجلات التي تحتوي على الأسماء:

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

الآن ، لكل سجل ، تحتاج إلى تحديد العام الذي تقابله في مهنة الرياضي - الأول أو الثاني أو الثالث أو العاشر. نقوم بعمل حلقة بكل الأسماء وحسابها.

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 

فيما يلي مثال على ما حدث: على ما يبدو ، لا تزال الأسماء نفسها قائمة. هذا أمر متوقع ، لكنه ليس مخيفًا ، لأننا سنحسب متوسط ​​كل شيء ، ولا ينبغي أن يكون هناك الكثير. بعد ذلك ، نبني مصفوفات للرسم البياني:
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]))

هذا كل شيء ، هناك أساس:



الآن ، لتزيينه بنقاط تتوافق مع نتائج محددة ، سنختار 1000 اسم عشوائي وننشئ صفائف مع النتائج الخاصة بها.

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']))

أضف حلقة لبناء الرسوم البيانية من هذه العينة العشوائية.

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

الآن كل شيء جاهز:



بشكل عام ، ليس صعبًا. ولكن هناك مشكلة واحدة. لحساب إكسب خبرة في الدورة، وجميع الأسماء، والتي هي تقريبا 200 ألف، واتخاذ ثماني ساعات. اضطررت إلى تصحيح الخوارزمية على عينات صغيرة ، ثم إجراء العملية الحسابية ليلا. من حيث المبدأ ، يمكن القيام بذلك مرة واحدة ، ولكن إذا وجدت نوعًا من الخطأ أو تريد تغيير شيء ما ، وتحتاج إلى إعادة سرده مرة أخرى ، فإنه يبدأ في الضغط. وهكذا ، عندما كنت سأنشر تقريرًا في المساء ، اتضح أنه من الضروري إعادة سرد كل شيء مرة أخرى. لم يكن الانتظار حتى الصباح جزءًا من خططي ، وبدأت أبحث عن طريقة لجعل الحساب أسرع. قررت موازية.

وجدت في مكان ما طريقة للقيام بذلك مع المعالجة المتعددة. من أجل العمل على Windows ، كنا بحاجة إلى وضع المنطق الرئيسي لكل مهمة متوازية في ملف worker.py منفصل :

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)

يتم نقل الإجراء إلى جزء من أسماء الأسماء ، الجزء datafreyma ar فقط مع هذه الأسماء ، والرقم التسلسلي للمهام المتوازية - num . تتم كتابة الحسابات في إطار البيانات ، وفي النهاية ، تتم كتابة إطار البيانات في الملف. في الكمبيوتر المحمول الذي يستدعي هذا العامل ، نقوم بإعداد الحجج وفقًا لذلك:

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])

نبدأ الحوسبة المتوازية:

from multiprocessing import Pool
import workers

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

وفي النهاية نقرأ النتائج من الملفات ونجمع القطع مرة أخرى في إطار البيانات بالكامل:

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])

وبالتالي ، كان من الممكن الحصول على تسارع 40 مرة ، وبدلاً من 8 ساعات لإكمال الحساب في 11 دقيقة ونشر تقرير في ذلك المساء. في الوقت نفسه ، تعلمت كيفية موازاة الثعبان ، أعتقد أنه سيكون مفيدًا. هنا ، تحول التسارع إلى أكثر من 8 أضعاف عدد النوى ، نظرًا لأن كل مهمة تستخدم إطار بيانات صغيرًا ، مما يجعل البحث أسرع. من حيث المبدأ ، يمكن تسريع الحسابات المتسلسلة بهذه الطريقة ، ولكن السؤال هو ، كيف تخمن؟

ومع ذلك ، لم أستطع الهدوء ، وحتى بعد النشر كنت أفكر باستمرار في كيفية إجراء الحساب باستخدام المتجه ، أي العمليات على أعمدة كاملة من إطار بيانات سلسلة الباندا. هذه الحسابات هي ترتيب حجم أسرع من أي دورات متوازية ، حتى على الكتلة الفائقة. وخرج. اتضح أنه لكل اسم للعثور على عام بداية المهنة ، من الضروري على العكس - لكل عام للعثور على المشاركين الذين بدأوا فيه. للقيام بذلك ، يجب عليك أولاً تحديد جميع الأسماء للسنة الأولى من نموذجنا ، وهذا هو عام 2010. وبناءً على ذلك ، نقوم بمعالجة جميع السجلات بهذه الأسماء باستخدام هذا العام. بعد ذلك ، نأخذ العام المقبل - 2011.

مرة أخرى نجد جميع الأسماء التي تحتوي على إدخالات هذا العام ، لكننا نأخذ منها فقط الأسماء غير المجهزة ، أي تلك التي لم يتم الوفاء بها في عام 2010 وتتم معالجتها باستخدامها في عام 2011. وهكذا بالنسبة لبقية العام. نفس الدورة ، ولكن ليس مائتي ألف تكرار ، ولكن تسعة في المجموع.

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

تنجز هذه الدورة في بضع ثوانٍ فقط. وتبين أن الرمز أكثر إيجازًا بكثير.

استنتاج


حسنًا ، أخيرًا ، تم إنجاز الكثير من العمل. بالنسبة لي ، كان هذا ، في الواقع ، أول مشروع من نوعه. عندما أخذتها ، كان الهدف الرئيسي هو التدرب على استخدام الثعبان ومكتباته. هذه المهمة أكثر من أن تكتمل. وكانت النتائج نفسها قابلة للعرض. ما هي النتائج التي استخلصتها لنفسي عند الانتهاء؟

أولاً: البيانات غير كاملة. ربما يكون هذا صحيحًا لأي مهمة تحليل تقريبًا. حتى إذا كانت منظمة تمامًا ، وغالبًا ما يحدث ذلك بشكل مختلف ، فأنت بحاجة إلى الاستعداد للتعامل معها قبل البدء في حساب الخصائص والبحث عن الاتجاهات - العثور على الأخطاء ، والقيم المتطرفة ، والانحرافات عن المعايير ، وما إلى ذلك.

ثانيًا:أي مهمة لها حل. هذا أشبه بالشعار ، لكنه في كثير من الأحيان. الأمر فقط أن هذا الحل قد لا يكون واضحًا جدًا ولا يكمن في البيانات نفسها ، ولكن خارج الصندوق ، إذا جاز التعبير. كمثال - معالجة أسماء المشاركين الموصوفة أعلاه ، أو كشط موقع الويب.

ثالثا: معرفة المجال أمر حاسم. سيجعل هذا من الممكن إعداد البيانات بشكل أفضل ، وإزالة البيانات غير الصحيحة أو غير القياسية الواضحة ، وتجنب أخطاء التفسير ، واستخدام المعلومات غير الموجودة في البيانات ، على سبيل المثال ، المسافات في هذا المشروع ، وتقديم النتائج في النموذج المقبول في المجتمع ، مع تجنب الاستنتاجات الغبية غير الصحيحة.

رابعاً: العمل في الثعبانهناك مجموعة غنية من الأدوات. في بعض الأحيان يبدو أنه يجدر التفكير في شيء ما ، تبدأ في البحث - إنه موجود بالفعل. هذا رائع! شكرًا جزيلاً للمبدعين على هذه المساهمة ، خاصةً للأدوات التي جاءت في متناول اليد هنا: السيلينيوم من أجل الكشط ، pycountry لتحديد رمز البلد وفقًا لمعيار ISO ، رموز البلد (datahub) للرموز الأولمبية ، geopy لتحديد الإحداثيات في العنوان ، folium - لتصور البيانات الجغرافية وتخمين الجنس - لتحليل الأسماء والمعالجة المتعددة - للحوسبة المتوازية و matplotlib و numpy ، وبالطبع الباندا - دون أن يكون هناك مكان للذهاب إليه.

خامساً: التوجه إلى كل شيء. من المهم للغاية أن تكون قادرًا على استخدام أدوات الباندا المدمجة ، فهي فعالة للغاية. أفترض ، في معظم الحالات ، عندما يتم قياس عدد السجلات بدءًا من عشرات الآلاف ، تصبح هذه المهارة ضرورية ببساطة.

السادس:معالجة البيانات فكرة سيئة. من الضروري محاولة تقليل أي تدخل يدوي - أولاً ، لا يتم قياسه ، أي عندما تزيد كمية البيانات عدة مرات ، سيزداد وقت المعالجة اليدوية إلى قيم غير مقبولة ، وثانيًا ، سيكون هناك تكرار متكرر - ستنسى شيئًا ، سترتكب خطأ في مكان ما . كل شيء برمجي فقط ، إذا كان هناك شيء خارج المعيار العام لحل البرنامج ، حسنًا ، لا بأس ، يمكنك التضحية بجزء من البيانات ، فسيظل هناك المزيد من الإيجابيات.

سابعا:يجب حفظ الكود في حالة صالحة للعمل. يبدو أنه يمكن أن يكون أكثر وضوحا! في الواقع ، عندما يتعلق الأمر بالتعليمات البرمجية الخاصة بك ، والتي تهدف إلى نشر نتائج هذا الرمز ، فكل شيء ليس صارمًا هنا. لقد عملت في Jupiter Notebooks ، وهذه البيئة ، في رأيي ، لا تحتاج إلى إنشاء منتجات برمجية متكاملة. تم تكوينه لإطلاق كل سطر على حدة ، وهذا له مزاياه - إنه سريع: التطوير والتصحيح والتنفيذ في نفس الوقت. ولكن غالبًا ما يكون الإغراء هو مجرد تعديل سطر ما والحصول بسرعة على نتيجة جديدة ، بدلاً من التكرار أو الالتفاف في الدفاع. بالطبع ، يجب تجنب مثل هذا الإغراء. يجب أن يسعى المرء للحصول على رمز جيد ، حتى "لنفسه" ، على الأقل لأنه حتى في عمل تحليلي واحد ، يتم الإطلاق عدة مرات ، ومن المؤكد أن استثمار الوقت في البداية سيؤتي ثماره في المستقبل. ويمكنك إضافة اختبارات ، حتى على أجهزة الكمبيوتر المحمولة ، في شكل تدقيق للمعلمات الهامة ورمي الاستثناءات - إنها مفيدة جدًا.

ثامناً: وفر أكثر. في كل خطوة ، قمت بحفظ نسخة جديدة من الملف. في المجموع ، تبين أنها حوالي 10. هذا أمر مريح ، لأنه عندما يتم اكتشاف خطأ ، فإنه يساعد على التحديد السريع في أي مرحلة حدث. بالإضافة إلى ذلك ، قمت بحفظ بيانات المصدر في الأعمدة المميزة بعلامة خام - وهذا يسمح لك بالتحقق من النتيجة بسرعة ورؤية التناقض.

تاسع:من الضروري قياس استثمار الوقت والنتيجة. في بعض الأماكن ، استغرقت وقتًا طويلاً جدًا لاستعادة البيانات ، والتي تشكل جزءًا من نسبة مئوية من الإجمالي. في الواقع ، هذا لا معنى له ، كان عليك فقط التخلص منهم ، وهذا كل شيء. وسأفعل ذلك إذا كان مشروعًا تجاريًا ، وليس تدريبًا ذاتيًا. هذا سيسمح لك بالحصول على النتيجة بشكل أسرع. يعمل مبدأ باريتو هنا - يتم تحقيق 80 ٪ من النتيجة في 20 ٪ من الوقت.

وآخر واحد:العمل في مثل هذه المشاريع يوسع الآفاق بشكل كبير. عن طيب خاطر ، تتعلم شيئًا جديدًا - على سبيل المثال ، أسماء دول غريبة - مثل جزر بيتكيرن ، أن رمز ISO لسويسرا هو CHE ، من اللاتينية "Confoederatio Helvetica" ، ما هو الاسم الإسباني ، حسنًا ، في الواقع عن الترياتلون نفسه - السجلات ، أصحابها ، أماكن السباقات ، تاريخ الأحداث وما إلى ذلك.

ربما يكفي. هذا كل شئ. شكرا لكل من قرأ حتى النهاية!

All Articles