Делаем data science-портфолио: история через данные
- воскресенье, 2 июля 2017 г. в 03:13:58
Перевод внезапно удачно попал в струю других датасайенсных туториалов на хабре. :)
Этот написан Виком Паручури, основателем Dataquest.io, где как раз и занимаются подобного рода интерактивным обучением data science и подготовкой к реальной работе в этой области. Каких-то эксклюзивных ноу-хау здесь нет, но очень подробно рассказан процесс от сбора данных до первичных выводов о них, что может быть интересно не только желающим составить резюме на data science, но и тем, кто просто хочет попробовать себя в практическом анализе, но не знает, с чего начать.
Data science-компании всё чаще смотрят портфолио, когда принимают решение о приёме на работу. Это, в частности, из-за того, что лучший способ судить о практических навыках — именно портфолио. И хорошая новость в том, что оно полностью в вашем распоряжении: если постараетесь – сможете собрать отличное портфолио, которым будут впечатлены многие компании.
Первый шаг в высококачественному портфолио – это понимание, какие умения в нём надо продемонстрировать.
Основные навыки, которые компании хотят видеть в data scientist-ах, и, соответственно, продемонстрированными в их портфолио, это:
Всякое хорошее портфолио содержит несколько проектов, каждый из которых может демонстрировать 1-2 данных пункта. Это первый пост из цикла, который будет рассматривать получение гармоничного data science-портфолио. Мы рассмотрим, как сделать первый проект для портфолио, и как рассказать хорошую историю через данные. В конце будет проект, который поможет раскрыть вашу способность общаться и способность делать заключения на основе данных.
Я точно не буду переводить весь цикл, но планирую коснуться интересного туториала о машинном обучении оттуда же.
В принципе, Data science вся состоит из общения. Вы видите в данных какую-то закономерность, потом ищете эффективный способ объяснить эту закономерность другим, потом убеждаете их предпринять те действия, которые вы считаете нужными. Одно из важнейших умений в data science — наглядно рассказать историю через данные. Удачная история может лучше преподнести ваши догадки и помочь другим понять ваши идеи.
История в контексте data science — это изложение всего того, что вы нашли и того, что это значит. Примером может служить открытие того, что прибыль вашей компании снизилась на 20% за последний год. Просто указать на этот факт недостаточно: надо объяснить, почему прибыль упала и что с этим делать.
Основные компоненты историй в данных это:
Лучшее средство доходчиво рассказать историю через данные — это Jupyter notebook. Если вы с ним незнакомы — тут хороший туториал. Jupyter notebook позволяет интерактивно исследовать данные и публиковать их на различных сайтах, включая гитхаб. Публикация результатов полезна для сотрудничества — ваш анализ смогут расширить другие люди.
Мы в этом посте будем использовать Jupyter notebook вместе с питоновскими библиотеками типа Pandas и matplotlib.
Первый шаг к созданию проекта — это определиться с темой. Стоит выбрать что-то, что вам интересно и что есть желание поисследовать. Всегда видно, когда люди сделали проект просто чтобы было, а когда потому, что им действительно интересно было покопаться в данных. На этом шаге имеет смысл потратить время, чтобы точно найти что-то увлекающее.
Хороший способ найти тему — полазить по разным датасетам и посмотреть, что есть интересного. Вот хорошие места для начала:
В реальной data science часто не получается найти полностью подготовленный для ваших изысканий датасет. Возможно, придётся агрегировать различные источники данных или серьёзно их чистить. Если тема вам очень интересна — имеет смысл сделать то же и здесь: лучше покажете себя в итоге.
Мы для поста будем использовать данные о Нью-Йоркских общеобразовательных школах, отсюда.
На всякий случай, приведу пример более близких к нам (россиянам) аналогичных датасетов:
Важно сделать весь проект от начала до конца. Для этого полезно ограничить область изучения так, чтобы точно знать, что вы закончите. Проще добавить что-то к уже завершённому проекту, чем пытаться закончить то, что уже попросту надоело доводить до конца.
В нашем случае, мы будем изучать оценки ЕГЭ старшеклассников вместе с различной демографической и прочей информацией о них. ЕГЭ или Единый Государственный экзамен — это тест, который старшеклассники сдают перед поступлением в колледж. Колледжи учитывают оценки, когда принимают решение о зачислении, так что хорошо сдать его — весьма важно. Экзамен состоит из трёх частей, каждая из которых оценивается в 800 баллов. Общий балл в итоге 2400 (хотя иногда это плавало туда-сюда - в датасете всё по 2400). Старшие школы часто оцениваются по среднему баллу ЕГЭ и высокий средний балл обычно является показателем того, насколько хорош школьный округ.
Были определённые жалобы на несправедливость оценок некоторым нацменьшинствам в США, поэтому анализ по Нью-Йорку поможет пролить свет на справедливость ЕГЭ.
Датасет с оценками ЕГЭ — здесь, а датасет с информацией по каждой школе — здесь. Это будет основой нашего проекта, но нам понадобится ещё информация, чтобы сделать полноценный анализ.
В оригинале экзамен называется SAT — Scholastic Aptitude Test. Но поскольку он практически по смыслу идентичен нашему ЕГЭ — решил так его и переводить.
Как только есть хорошая тема — полезно просмотреть другие датасеты, которые могут расширить тему или помочь углубить исследование. Лучше делать это в начале, чтобы было как можно больше данных для исследования по мере создания проекта. Если данных будет мало — есть шанс, что вы сдадитесь слишком рано.
В нашем случае, есть ещё несколько датасетов по этой теме на том же сайте, которые освещают демографическую информацию и результаты экзаменов.
Вот ссылки на все датасеты, что будем использовать:
Все эти данные связаны между собой, и мы сможем их объединить прежде, чем начнём анализ.
Прежде, чем погружаться в анализ данных, полезно выяснить общую информацию о предмете. В нашем случае, мы знаем кое-что, что может оказаться полезным:
То, что я перевёл как "Районы" на самом деле называется в NYC "боро", и столбцы, соответственно, называются Borough.
Чтобы действительно понять контекст данных, нужно потратить время и об этих данных почитать. В нашем случае, каждая вышеприведённая ссылка содержит описание данных для каждой колонки. Похоже, у нас есть данные по оценкам ЕГЭ старшеклассников, вместе с другими датасетами, которые содержат демографическую и прочую информацию.
Запустим кое-какой код, чтобы прочесть данные. Используем Jupyter notebook для наших исследований. Нижеприведённый код:
import pandas
import numpy as np
files = ["ap_2010.csv", "class_size.csv", "demographics.csv", "graduation.csv", "hs_directory.csv", "math_test_results.csv", "sat_results.csv"]
data = {}
for f in files:
d = pandas.read_csv("schools/{0}".format(f))
data[f.replace(".csv", "")] = d
Как только мы всё прочитали, можно использовать на датафреймах метод head, чтобы вывести первые 5 строк каждого:
for k,v in data.items():
print("\n" + k + "\n")
print(v.head())
Уже можно видеть в датасетах определённые особенности:
math_test_results
DBN | Grade | Year | Category | Number Tested | Mean Scale Score | Level 1 # | \ | |
---|---|---|---|---|---|---|---|---|
0 | 01M015 | 3 | 2006 | All Students | 39 | 667 | 2 | |
1 | 01M015 | 3 | 2007 | All Students | 31 | 672 | 2 | |
2 | 01M015 | 3 | 2008 | All Students | 37 | 668 | 0 | |
3 | 01M015 | 3 | 2009 | All Students | 33 | 668 | 0 | |
4 | 01M015 | 3 | 2010 | All Students | 26 | 677 | 6 |
Level 1 % | Level 2 # | Level 2 % | Level 3 # | Level 3 % | Level 4 # | Level 4 % | \ | |
---|---|---|---|---|---|---|---|---|
0 | 5.1% | 11 | 28.2% | 20 | 51.3% | 6 | 15.4% | |
1 | 6.5% | 3 | 9.7% | 22 | 71% | 4 | 12.9% | |
2 | 0% | 6 | 16.2% | 29 | 78.4% | 2 | 5.4% | |
3 | 0% | 4 | 12.1% | 28 | 84.8% | 1 | 3% | |
4 | 23.1% | 12 | 46.2% | 6 | 23.1% | 2 | 7.7% |
Level 3+4 # | Level 3+4 % | |
---|---|---|
0 | 26 | 66.7% |
1 | 26 | 83.9% |
2 | 31 | 83.8% |
3 | 29 | 87.9% |
4 | 8 | 30.8% |
ap_2010
DBN | SchoolName | AP Test Takers | Total Exams Taken | Number of Exams with scores 3 4 or 5 | |
---|---|---|---|---|---|
0 | 01M448 | UNIVERSITY NEIGHBORHOOD H.S. | 39 | 49 | 10 |
1 | 01M450 | EAST SIDE COMMUNITY HS | 19 | 21 | s |
2 | 01M515 | LOWER EASTSIDE PREP | 24 | 26 | 24 |
3 | 01M539 | NEW EXPLORATIONS SCI,TECH,MATH | 255 | 377 | 191 |
4 | 02M296 | High School of Hospitality Management | s | s | s |
sat_results
DBN | SCHOOL NAME | Num of SAT Test Takers | SAT Critical Reading Avg. Score | SAT Math Avg. Score | SAT Writing Avg. Score | |
---|---|---|---|---|---|---|
0 | 01M292 | HENRY STREET SCHOOL FOR INTERNATIONAL STUDIES | 29 | 355 | 404 | 363 |
1 | 01M448 | UNIVERSITY NEIGHBORHOOD HIGH SCHOOL | 91 | 383 | 423 | 366 |
2 | 01M450 | EAST SIDE COMMUNITY SCHOOL | 70 | 377 | 402 | 370 |
3 | 01M458 | FORSYTH SATELLITE ACADEMY | 7 | 414 | 401 | 359 |
4 | 01M509 | MARTA VALLE HIGH SCHOOL | 44 | 390 | 433 | 384 |
class_size
CSD | BOROUGH | SCHOOL CODE | SCHOOL NAME | GRADE | PROGRAM TYPE | CORE SUBJECT (MS CORE and 9-12 ONLY) | CORE COURSE (MS CORE and 9-12 ONLY) | \ | |
---|---|---|---|---|---|---|---|---|---|
0 | 1 | M | M015 | P.S. 015 Roberto Clemente | 0K | GEN ED | - | - | |
1 | 1 | M | M015 | P.S. 015 Roberto Clemente | 0K | CTT | - | - | |
2 | 1 | M | M015 | P.S. 015 Roberto Clemente | 01 | GEN ED | - | - | |
3 | 1 | M | M015 | P.S. 015 Roberto Clemente | 01 | CTT | - | - | |
4 | 1 | M | M015 | P.S. 015 Roberto Clemente | 02 | GEN E | - | - |
SERVICE CATEGORY(K-9* ONLY) | NUMBER OF STUDENTS / SEATS FILLED | NUMBER OF SECTIONS | AVERAGE CLASS SIZE | SIZE OF SMALLEST CLASS | \ | |
---|---|---|---|---|---|---|
0 | - | 19.0 | 1.0 | 19.0 | 19.0 | |
1 | - | 21.0 | 1.0 | 21.0 | 21.0 | |
2 | - | 17.0 | 1.0 | 17.0 | 17.0 | |
3 | - | 17.0 | 1.0 | 17.0 | 17.0 | |
4 | - | 15.0 | 1.0 | 15.0 | 15.0 |
SIZE OF LARGEST CLASS | DATA SOURCE | SCHOOLWIDE PUPIL-TEACHER RATIO | |
---|---|---|---|
0 | 19.0 | ATS | NaN |
1 | 21.0 | ATS | NaN |
2 | 17.0 | ATS | NaN |
3 | 17.0 | ATS | NaN |
4 | 15.0 | ATS | NaN |
demographics
DBN | Name | schoolyear | fl_percent | frl_percent | \ | |
---|---|---|---|---|---|---|
0 | 01M015 | P.S. 015 ROBERTO CLEMENTE | 20052006 | 89.4 | NaN | |
1 | 01M015 | P.S. 015 ROBERTO CLEMENTE | 20062007 | 89.4 | NaN | |
2 | 01M015 | P.S. 015 ROBERTO CLEMENTE | 20072008 | 89.4 | NaN | |
3 | 01M015 | P.S. 015 ROBERTO CLEMENTE | 20082009 | 89.4 | NaN | |
4 | 01M015 | P.S. 015 ROBERTO CLEMENTE | 20092010 | 96.5 |
total_enrollment | prek | k | grade1 | grade2 | ... | black_num | black_per | \ | |
---|---|---|---|---|---|---|---|---|---|
0 | 281 | 15 | 36 | 40 | 33 | ... | 74 | 26.3 | |
1 | 243 | 15 | 29 | 39 | 38 | ... | 68 | 28.0 | |
2 | 261 | 18 | 43 | 39 | 36 | ... | 77 | 29.5 | |
3 | 252 | 17 | 37 | 44 | 32 | ... | 75 | 29.8 | |
4 | 208 | 16 | 40 | 28 | 32 | ... | 67 | 32.2 |
hispanic_num | hispanic_per | white_num | white_per | male_num | male_per | female_num | female_per | \ | |
---|---|---|---|---|---|---|---|---|---|
0 | 189 | 67.3 | 5 | 1.8 | 158.0 | 56.2 | 123.0 | 43.8 | |
1 | 153 | 63.0 | 4 | 1.6 | 140.0 | 57.6 | 103.0 | 42.4 | |
2 | 157 | 60.2 | 7 | 2.7 | 143.0 | 54.8 | 118.0 | 45.2 | |
3 | 149 | 59.1 | 7 | 2.8 | 149.0 | 59.1 | 103.0 | 40.9 | |
4 | 118 | 56.7 | 6 | 2.9 | 124.0 | 59.6 | 84.0 | 40.4 |
graduation
Demographic | DBN | School Name | Cohort | \ | |
---|---|---|---|---|---|
0 | Total Cohort | 01M292 | HENRY STREET SCHOOL FOR INTERNATIONAL | 2003 | |
1 | Total Cohort | 01M292 | HENRY STREET SCHOOL FOR INTERNATIONAL | 2004 | |
2 | Total Cohort | 01M292 | HENRY STREET SCHOOL FOR INTERNATIONAL | 2005 | |
3 | Total Cohort | 01M292 | HENRY STREET SCHOOL FOR INTERNATIONAL | 2006 | |
4 | Total Cohort | 01M292 | HENRY STREET SCHOOL FOR INTERNATIONAL | 2006 Aug |
Total Cohort | Total Grads — n | Total Grads — % of cohort | Total Regents — n | \ | |
---|---|---|---|---|---|
0 | 5 | s | s | s | |
1 | 55 | 37 | 67.3% | 17 | |
2 | 64 | 43 | 67.2% | 27 | |
3 | 78 | 43 | 55.1% | 36 | |
4 | 78 | 44 | 56.4% | 37 |
Total Regents — % of cohort | Total Regents — % of grads | ... | Regents w/o Advanced — n | \ | |
---|---|---|---|---|---|
0 | s | s | ... | s | |
1 | 30.9% | 45.9% | ... | 17 | |
2 | 42.2% | 62.8% | ... | 27 | |
3 | 46.2% | 83.7% | ... | 36 | |
4 | 47.4% | 84.1% | ... | 37 |
Regents w/o Advanced — % of cohort | Regents w/o Advanced — % of grads | \ | |
---|---|---|---|
0 | s | s | |
1 | 30.9% | 45.9% | |
2 | 42.2% | 62.8% | |
3 | 46.2% | 83.7% | |
4 | 47.4% | 84.1% |
Local — n | Local — % of cohort | Local — % of grad | s Still Enrolled — n | \ | |
---|---|---|---|---|---|
0 | s | s | s | s | |
1 | 20 | 36.4% | 54.1% | 15 | |
2 | 16 | 25% | 37.200000000000003% | 9 | |
3 | 7 | 9% | 16.3% | 16 | |
4 | 7 | 9% | 15.9% | 15 |
Still Enrolled — % of cohort | Dropped Out — n | Dropped Out — % of cohort | |
---|---|---|---|
0 | s | s | s |
1 | 27.3% | 3 | 5.5% |
2 | 14.1% | 9 | 14.1% |
3 | 20.5% | 11 | 14.1% |
4 | 19.2% | 11 | 14.1% |
hs_directory
dbn | school_name | boro | \ | |
---|---|---|---|---|
0 | 17K548 | Brooklyn School for Music & Theatre | Brooklyn | |
1 | 09X543 | High School for Violin and Dance | Bronx | |
2 | 09X327 | Comprehensive Model School Project M.S. 327 | Bronx | |
3 | 02M280 | Manhattan Early College School for Advertising | Manhattan | |
4 | 28Q680 | Queens Gateway to Health Sciences Secondary Sc... | Queens |
building_code | phone_number | fax_number | grade_span_min | grade_span_max | \ | |
---|---|---|---|---|---|---|
0 | K440 | 718-230-6250 | 718-230-6262 | 9 | 12 | |
1 | X400 | 718-842-0687 | 718-589-9849 | 9 | 12 | |
2 | X240 | 718-294-8111 | 718-294-8109 | 6 | 12 | |
3 | M520 | 718-935-3477 | NaN | 9 | 10 | |
4 | Q695 | 718-969-3155 | 718-969-3552 | 6 | 12 |
expgrade_span_min | expgrade_span_max | ... | priority02 | \ | |
---|---|---|---|---|---|
0 | NaN | NaN | ... | Then to New York City residents | |
1 | NaN | NaN | ... | Then to New York City residents who attend an ... | |
2 | NaN | NaN | ... | Then to Bronx students or residents who attend... | |
3 | 9 | 14.0 | ... | Then to New York City residents who attend an ... | |
4 | NaN | NaN | ... | Then to Districts 28 and 29 students or residents |
priority03 | priority04 | priority05 | \ | |
---|---|---|---|---|
0 | NaN | NaN | NaN | |
1 | Then to Bronx students or residents | Then to New York City residents | NaN | |
2 | Then to New York City residents who attend an ... | Then to Bronx students or residents | Then to New York City residents | |
3 | Then to Manhattan students or residents | Then to New York City residents | NaN | |
4 | Then to Queens students or residents | Then to New York City residents | NaN |
priority06 | priority07 | priority08 | priority09 | priority10 | Location 1 | |
---|---|---|---|---|---|---|
0 | NaN | NaN | NaN | NaN | NaN | 883 Classon Avenue\nBrooklyn, NY 11225\n(40.67... |
1 | NaN | NaN | NaN | NaN | NaN | 1110 Boston Road\nBronx, NY 10456\n(40.8276026... |
2 | NaN | NaN | NaN | NaN | NaN | 1501 Jerome Avenue\nBronx, NY 10452\n(40.84241... |
3 | NaN | NaN | NaN | NaN | NaN | 411 Pearl Street\nNew York, NY 10038\n(40.7106... |
4 | NaN | NaN | NaN | NaN | NaN | 160-20 Goethals Avenue\nJamaica, NY 11432\n(40... |
Чтобы с данными работать было легче, нам надо объединить все датасеты в один — это позволит нам быстро сравнивать колонки в датасетах. Для этого, прежде всего, надо найти общую колонку для объединения. Глядя на то, что нам ранее вывелось, можно предположить, что такой колонкой может быть DBN, поскольку она повторяется в нескольких датасетах.
Если мы загуглим "DBN New York City Schools", то придём сюда, где объясняется, что DBN — это уникальный код для каждой школы. При исследовании датасетов, особенно правительственных, приходится частенько проделать детективную работу, чтобы понять, что значит каждый столбец, даже каждый датасет иногда.
Теперь проблема в том, что два датасета, class_size и hs_directory — не содержат DBN. В hs_directory он называется dbn, поэтому просто переименуем или скопируем его в DBN. Для class_size нужен будет другой подход.
Столбец DBN выглядит так:
In [5]: data["demographics"]["DBN"].head()
Out[5]:
0 01M015
1 01M015
2 01M015
3 01M015
4 01M015
Name: DBN, dtype: object
Если посмотрим на class_size - вот то, что мы увидим в первых 5 строках:
In [4]:
data["class_size"].head()
Out[4]:
CSD | BOROUGH | SCHOOL CODE | SCHOOL NAME | GRADE | PROGRAM TYPE | CORE SUBJECT (MS CORE and 9-12 ONLY) | / | |
---|---|---|---|---|---|---|---|---|
0 | 1 | M | M015 | P.S. 015 Roberto Clemente | 0K | GEN ED | - | |
1 | 1 | M | M015 | P.S. 015 Roberto Clemente | 0K | CTT | - | |
2 | 1 | M | M015 | P.S. 015 Roberto Clemente | 01 | GEN ED | - | |
3 | 1 | M | M015 | P.S. 015 Roberto Clemente | 01 | CTT | - | |
4 | 1 | M | M015 | P.S. 015 Roberto Clemente | 02 | GEN ED | - |
CORE COURSE (MS CORE and 9-12 ONLY) | SERVICE CATEGORY(K-9* ONLY) | NUMBER OF STUDENTS / SEATS FILLED | / | |
---|---|---|---|---|
0 | - | - | 19.0 | |
1 | - | - | 21.0 | |
2 | - | - | 17.0 | |
3 | - | - | 17.0 | |
4 | - | - | 15.0 |
NUMBER OF SECTIONS | AVERAGE CLASS SIZE | SIZE OF SMALLEST CLASS | SIZE OF LARGEST CLASS | DATA SOURCE | SCHOOLWIDE PUPIL-TEACHER RATIO | |
---|---|---|---|---|---|---|
0 | 1.0 | 19.0 | 19.0 | 19.0 | ATS | NaN |
1 | 1.0 | 21.0 | 21.0 | 21.0 | ATS | NaN |
2 | 1.0 | 17.0 | 17.0 | 17.0 | ATS | NaN |
3 | 1.0 | 17.0 | 17.0 | 17.0 | ATS | NaN |
4 | 1.0 | 15.0 | 15.0 | 15.0 | ATS | NaN |
Как можно заметить, DBN — это просто комбинация CSD,BOROUGH и SCHOOL_ CODE. Для незнакомых с Нью-Йорком: он состоит из 5 районов. Каждый район — это организационная единица, приблизительно равная по размеру достаточно большому городу США. DBN расшифровывается как районно-окружной номер. Похоже, что CSD — это округ, BOROUGH — район и в сочетании со SCHOOL_CODE получается DBN.
Теперь, когда мы знаем, как составить DBN, мы можем добавить его в class_size и hs_directory.
In [ ]:
data["class_size"]["DBN"] = data["class_size"].apply(lambda x: "{0:02d}{1}".format(x["CSD"], x["SCHOOL CODE"]), axis=1)
data["hs_directory"]["DBN"] = data["hs_directory"]["dbn"]
Один из самых потенциально интересных датасетов — это датасет опросов учеников, родителей и учителей по поводу качества школ. Эти опросы включают информацию о субъективном восприятии безопасности каждой школы, учебных стандартах и прочем. Перед тем, как объединять наши датасеты, давайте добавим данные по опросам. В реальных data science-проектах вы часто будете натыкаться на интересные данные по ходу анализа и, возможно, захотите так же их подключить. Гибкий инструмент, типа Jupyter notebook, позволяет быстро добавить дополнительный код и переделать анализ.
В нашем случае, добавим дополнительные данные по опросам в наш словарь data, после чего объединим все датасеты. Данные по опросам состоят из двух файлов, один на все школы, и один на школьный округ 75. Чтобы их объединить, потребуется написать немного кода. В нём мы сделаем что:
In [66]:
survey1 = pandas.read_csv("schools/survey_all.txt", delimiter="\t", encoding='windows-1252')
survey2 = pandas.read_csv("schools/survey_d75.txt", delimiter="\t", encoding='windows-1252')
survey1["d75"] = False
survey2["d75"] = True
survey = pandas.concat([survey1, survey2], axis=0)
Как только мы объединим все опросы, появится дополнительная трудность. Мы хотим минимизировать количество столбцов в нашем объединённом датасете, чтобы можно было легко сравнивать колонки и выявлять зависимости. К несчастью, данные по опросам содержат много ненужных нам колонок:
In [16]:
survey.head()
Out[16]:
N_p | N_s | N_t | aca_p_11 | aca_s_11 | aca_t_11 | aca_tot_11 | / | |
---|---|---|---|---|---|---|---|---|
0 | 90.0 | NaN | 22.0 | 7.8 | NaN | 7.9 | 7.9 | |
1 | 161.0 | NaN | 34.0 | 7.8 | NaN | 9.1 | 8.4 | |
2 | 367.0 | NaN | 42.0 | 8.6 | NaN | 7.5 | 8.0 | |
3 | 151.0 | 145.0 | 29.0 | 8.5 | 7.4 | 7.8 | 7.9 | |
4 | 90.0 | NaN | 23.0 | 7.9 | NaN | 8.1 | 8.0 |
bn | com_p_11 | com_s_11 | ... | t_q8c_1 | t_q8c_2 | t_q8c_3 | t_q8c_4 | / | |
---|---|---|---|---|---|---|---|---|---|
0 | M015 | 7.6 | NaN | ... | 29.0 | 67.0 | 5.0 | 0.0 | |
1 | M019 | 7.6 | NaN | ... | 74.0 | 21.0 | 6.0 | 0.0 | |
2 | M020 | 8.3 | NaN | ... | 33.0 | 35.0 | 20.0 | 13.0 | |
3 | M034 | 8.2 | 5.9 | ... | 21.0 | 45.0 | 28.0 | 7.0 | |
4 | M063 | 7.9 | NaN | ... | 59.0 | 36.0 | 5.0 | 0.0 |
t_q9 | t_q9_1 | t_q9_2 | t_q9_3 | t_q9_4 | t_q9_5 | |
---|---|---|---|---|---|---|
0 | NaN | 5.0 | 14.0 | 52.0 | 24.0 | 5.0 |
1 | NaN | 3.0 | 6.0 | 3.0 | 78.0 | 9.0 |
2 | NaN | 3.0 | 5.0 | 16.0 | 70.0 | 5.0 |
3 | NaN | 0.0 | 18.0 | 32.0 | 39.0 | 11.0 |
4 | NaN | 10.0 | 5.0 | 10.0 | 60.0 | 15.0 |
Мы справимся с этим, заглянув в файл со словарём данных, который мы скачали вместе с данными по опросам. Он расскажет нам про важные поля:
А потом мы удалим все не относящиеся к нам столбцы в survey:
In [17]:
survey["DBN"] = survey["dbn"]
survey_fields = ["DBN", "rr_s", "rr_t", "rr_p", "N_s", "N_t", "N_p", "saf_p_11", "com_p_11", "eng_p_11", "aca_p_11", "saf_t_11", "com_t_11", "eng_t_10", "aca_t_11", "saf_s_11", "com_s_11", "eng_s_11", "aca_s_11", "saf_tot_11", "com_tot_11", "eng_tot_11", "aca_tot_11",]
survey = survey.loc[:,survey_fields]
data["survey"] = survey
survey.shape
Out[17]:
(1702, 23)
Понимание того, что именно содержит каждый датасет и какие столбцы из него важны, может сэкономить кучу времени и усилий в дальнейшем.
Если мы взглянем на некоторые датасеты, включая class_size, мы сразу увидим проблему:
In [18]: data["class_size"].head()
Out[18]:
CSD | BOROUGH | SCHOOL CODE | SCHOOL NAME | GRADE | PROGRAM TYPE | CORE SUBJECT (MS CORE and 9-12 ONLY) | / | |
---|---|---|---|---|---|---|---|---|
0 | 1 | M | M015 | P.S. 015 Roberto Clemente | 0K | GEN ED | - | |
1 | 1 | M | M015 | P.S. 015 Roberto Clemente | 0K | CTT | - | |
2 | 1 | M | M015 | P.S. 015 Roberto Clemente | 01 | GEN ED | - | |
3 | 1 | M | M015 | P.S. 015 Roberto Clemente | 01 | CTT | - | |
4 | 1 | M | M015 | P.S. 015 Roberto Clemente | 02 | GEN ED | - |
CORE COURSE (MS CORE and 9-12 ONLY) | SERVICE CATEGORY(K-9* ONLY) | NUMBER OF STUDENTS / SEATS FILLED | NUMBER OF SECTIONS | AVERAGE CLASS SIZE | / | |
---|---|---|---|---|---|---|
0 | - | - | 19.0 | 1.0 | 19.0 | |
1 | - | - | 21.0 | 1.0 | 21.0 | |
2 | - | - | 17.0 | 1.0 | 17.0 | |
3 | - | - | 17.0 | 1.0 | 17.0 | |
4 | - | - | 15.0 | 1.0 | 15.0 |
SIZE OF SMALLEST CLASS | SIZE OF LARGEST CLASS | DATA SOURCE | SCHOOLWIDE PUPIL-TEACHER RATIO | DBN | |
---|---|---|---|---|---|
0 | 19.0 | 19.0 | ATS | NaN | 01M015 |
1 | 21.0 | 21.0 | ATS | NaN | 01M015 |
2 | 17.0 | 17.0 | ATS | NaN | 01M015 |
3 | 17.0 | 17.0 | ATS | NaN | 01M015 |
4 | 15.0 | 15.0 | ATS | NaN | 01M015 |
Для каждой школы есть несколько строк (что можно понять по повторяющимся полям DBN и SCHOOL NAME). Хотя, если мы взглянем на sat_results - в нём только по одной строке на школу:
In [21]:
data["sat_results"].head()
Out[21]:
DBN | SCHOOL NAME | Num of SAT Test Takers | SAT Critical Reading Avg. Score | SAT Math Avg. Score | SAT Writing Avg. Score | |
---|---|---|---|---|---|---|
0 | 01M292 | HENRY STREET SCHOOL FOR INTERNATIONAL STUDIES | 29 | 355 | 404 | 363 |
1 | 01M448 | UNIVERSITY NEIGHBORHOOD HIGH SCHOOL | 91 | 383 | 423 | 366 |
2 | 01M450 | EAST SIDE COMMUNITY SCHOOL | 70 | 377 | 402 | 370 |
3 | 01M458 | FORSYTH SATELLITE ACADEMY | 7 | 414 | 401 | 359 |
4 | 01M509 | MARTA VALLE HIGH SCHOOL | 44 | 390 | 433 | 384 |
Чтобы объединить эти датасеты, нужен способ уплотнить датасеты типа class_size так, чтобы в них было по одной строке на каждую старшую школу. Если не получится — то не получится и сравнить оценки ЕГЭ с размерами класса. Мы сможем этого достичь, получше разобравшись в данных, а потом сделав некоторые агрегации.
По датасету class_size - похоже, что GRADE и PROGRAM TYPE содержат разные оценки по каждой школе. Ограничив каждое поле единственным значением, мы сможем отбросить все строчки-дубликаты. В коде ниже мы:
In [68]:
class_size = data["class_size"]
class_size = class_size[class_size["GRADE "] == "09-12"]
class_size = class_size[class_size["PROGRAM TYPE"] == "GEN ED"]
class_size = class_size.groupby("DBN").agg(np.mean)
class_size.reset_index(inplace=True)
data["class_size"] = class_size
Дальше нам надо ужать датасет demographics. Данные собраны за несколько лет по одним и тем же школам. Мы выберем только те строки, где поле schoolyear свежее всего.
In [69]:
demographics = data["demographics"]
demographics = demographics[demographics["schoolyear"] == 20112012]
data["demographics"] = demographics
Теперь нам нужно сжать датасет math_test_results. Он делится по значениям Grade и Year. Мы можем выбрать единственный класс за единственный год:
In [70]:
data["math_test_results"] = data["math_test_results"][data["math_test_results"]["Year"] == 2011]
data["math_test_results"] = data["math_test_results"][data["math_test_results"]["Grade"] ==
Наконец, graduation тоже надо уплотнить:
In [71]:
data["graduation"] = data["graduation"][data["graduation"]["Cohort"] == "2006"]
data["graduation"] = data["graduation"][data["graduation"]["Demographic"] == "Total Cohort"]
Очистка и исследование данных критичны перед работой над сутью проекта. Хороший, годныйцелостный датасет поможет быстрее сделать анализ.
Вычисление переменных может ускорить наш анализ возможностью делать сравнивание быстрее и в принципе давая возможность делать некоторые, невозможные без них, сравнения. Первое, что мы можем сделать — посчитать общий балл ЕГЭ из отдельных колонок SAT Math Avg. Score, SAT Critical Reading Avg. Score, и SAT Writing Avg. Score. В коде ниже мы:
In [72]:
cols = ['SAT Math Avg. Score', 'SAT Critical Reading Avg. Score', 'SAT Writing Avg. Score']
for c in cols:
data["sat_results"][c] = data["sat_results"][c].convert_objects(convert_numeric=True)
data['sat_results']['sat_score'] = data['sat_results'][cols[0]] + data['sat_results'][cols[1]]
Далее нам надо распарсить координаты каждой школы, чтобы делать карты. Они позволят нам отметить положение каждой школы. В коде мы:
Выведем наши датасеты, посмотрим, что получилось:
In [74]:
for k,v in data.items():
print(k)
print(v.head())
math_test_results
DBN | Grade | Year | Category | Number Tested | Mean Scale Score | \ | |
---|---|---|---|---|---|---|---|
111 | 01M034 | 8 | 2011 | All Students | 48 | 646 | |
280 | 01M140 | 8 | 2011 | All Students | 61 | 665 | |
346 | 01M184 | 8 | 2011 | All Students | 49 | 727 | |
388 | 01M188 | 8 | 2011 | All Students | 49 | 658 | |
411 | 01M292 | 8 | 2011 | All Students | 49 | 650 |
Level 1 # | Level 1 % | Level 2 # | Level 2 % | Level 3 # | Level 3 % | Level 4 # | \ | |
---|---|---|---|---|---|---|---|---|
111 | 15 | 31.3% | 22 | 45.8% | 11 | 22.9% | 0 | |
280 | 1 | 1.6% | 43 | 70.5% | 17 | 27.9% | 0 | |
346 | 0 | 0% | 0 | 0% | 5 | 10.2% | 44 | |
388 | 10 | 20.4% | 26 | 53.1% | 10 | 20.4% | 3 | |
411 | 15 | 30.6% | 25 | 51% | 7 | 14.3% | 2 |
Level 4 % | Level 3+4 # | Level 3+4 % | |
---|---|---|---|
111 | 0% | 11 | 22.9% |
280 | 0% | 17 | 27.9% |
346 | 89.8% | 49 | 100% |
388 | 6.1% | 13 | 26.5% |
411 | 4.1% | 9 | 18.4% |
survey
DBN | rr_s | rr_t | rr_p | N_s | N_t | N_p | saf_p_11 | com_p_11 | eng_p_11 | \ | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 01M015 | NaN | 88 | 60 | NaN | 22.0 | 90.0 | 8.5 | 7.6 | 7.5 | |
1 | 01M019 | NaN | 100 | 60 | NaN | 34.0 | 161.0 | 8.4 | 7.6 | 7.6 | |
2 | 01M020 | NaN | 88 | 73 | NaN | 42.0 | 367.0 | 8.9 | 8.3 | 8.3 | |
3 | 01M034 | 89.0 | 73 | 50 | 145.0 | 29.0 | 151.0 | 8.8 | 8.2 | 8.0 | |
4 | 01M063 | NaN | 100 | 60 | NaN | 23.0 | 90.0 | 8.7 | 7.9 | 8.1 |
... | eng_t_10 | aca_t_11 | saf_s_11 | com_s_11 | eng_s_11 | aca_s_11 | \ | |
---|---|---|---|---|---|---|---|---|
0 | ... | NaN | 7.9 | NaN | NaN | NaN | NaN | |
1 | ... | NaN | 9.1 | NaN | NaN | NaN | NaN | |
2 | ... | NaN | 7.5 | NaN | NaN | NaN | NaN | |
3 | ... | NaN | 7.8 | 6.2 | 5.9 | 6.5 | 7.4 | |
4 | ... | NaN | 8.1 | NaN | NaN | NaN | NaN |
saf_tot_11 | com_tot_11 | eng_tot_11 | aca_tot_11 | |
---|---|---|---|---|
0 | 8.0 | 7.7 | 7.5 | 7.9 |
1 | 8.5 | 8.1 | 8.2 | 8.4 |
2 | 8.2 | 7.3 | 7.5 | 8.0 |
3 | 7.3 | 6.7 | 7.1 | 7.9 |
4 | 8.5 | 7.6 | 7.9 | 8.0 |
ap_2010
DBN | SchoolName | AP Test Takers | Total Exams Taken | Number of Exams with scores 3 4 or 5 | |
---|---|---|---|---|---|
0 | 01M448 | UNIVERSITY NEIGHBORHOOD H.S. | 39 | 49 | 10 |
1 | 01M450 | EAST SIDE COMMUNITY HS | 19 | 21 | s |
2 | 01M515 | LOWER EASTSIDE PREP | 24 | 26 | 24 |
3 | 01M539 | NEW EXPLORATIONS SCI,TECH,MATH | 255 | 377 | 191 |
4 | 02M296 | High School of Hospitality Management | s | s | s |
sat_results
DBN | SCHOOL NAME | Num of SAT Test Takers | SAT Critical Reading Avg. Score | \ | |
---|---|---|---|---|---|
0 | 01M292 | HENRY STREET SCHOOL FOR INTERNATIONAL STUDIES | 29 | 355.0 | |
1 | 01M448 | UNIVERSITY NEIGHBORHOOD HIGH SCHOOL | 91 | 383.0 | |
2 | 01M450 | EAST SIDE COMMUNITY SCHOOL | 70 | 377.0 | |
3 | 01M458 | FORSYTH SATELLITE ACADEMY | 7 | 414.0 | |
4 | 01M509 | MARTA VALLE HIGH SCHOOL | 44 | 390.0 |
SAT Math Avg. Score | SAT Writing Avg. Score | sat_score | |
---|---|---|---|
0 | 404.0 | 363.0 | 1122.0 |
1 | 423.0 | 366.0 | 1172.0 |
2 | 402.0 | 370.0 | 1149.0 |
3 | 401.0 | 359.0 | 1174.0 |
4 | 433.0 | 384.0 | 1207.0 |
class_size
DBN | CSD | NUMBER OF STUDENTS / SEATS FILLED | NUMBER OF SECTIONS | \ | |
---|---|---|---|---|---|
0 | 01M292 | 1 | 88.0000 | 4.000000 | |
1 | 01M332 | 1 | 46.0000 | 2.000000 | |
2 | 01M378 | 1 | 33.0000 | 1.000000 | |
3 | 01M448 | 1 | 105.6875 | 4.750000 | |
4 | 01M450 | 1 | 57.6000 | 2.733333 |
AVERAGE CLASS SIZE | SIZE OF SMALLEST CLASS | SIZE OF LARGEST CLASS | SCHOOLWIDE PUPIL-TEACHER RATIO | |
---|---|---|---|---|
0 | 22.564286 | 18.50 | 26.571429 | NaN |
1 | 22.000000 | 21.00 | 23.500000 | NaN |
2 | 33.000000 | 33.00 | 33.000000 | NaN |
3 | 22.231250 | 18.25 | 27.062500 | NaN |
4 | 21.200000 | 19.40 | 22.866667 | NaN |
demographics
DBN | Name | schoolyear | \ | |
---|---|---|---|---|
6 | 01M015 | P.S. 015 ROBERTO CLEMENTE | 20112012 | |
13 | 01M019 | P.S. 019 ASHER LEVY | 20112012 | |
20 | 01M020 | PS 020 ANNA SILVER | 20112012 | |
27 | 01M034 | PS 034 FRANKLIN D ROOSEVELT | 20112012 | |
35 | 01M063 | PS 063 WILLIAM MCKINLEY | 20112012 |
fl_percent | frl_percent | total_enrollment | prek | k | grade1 | grade2 | \ | |
---|---|---|---|---|---|---|---|---|
6 | NaN | 89.4 | 189 | 13 | 31 | 35 | 28 | |
13 | NaN | 61.5 | 328 | 32 | 46 | 52 | 54 | |
20 | NaN | 92.5 | 626 | 52 | 102 | 121 | 87 | |
27 | NaN | 99.7 | 401 | 14 | 34 | 38 | 36 | |
35 | NaN | 78.9 | 176 | 18 | 20 | 30 | 21 |
... | black_num | black_per | hispanic_num | hispanic_per | white_num | \ | |
---|---|---|---|---|---|---|---|
6 | ... | 63 | 33.3 | 109 | 57.7 | 4 | |
13 | ... | 81 | 24.7 | 158 | 48.2 | 28 | |
20 | ... | 55 | 8.8 | 357 | 57.0 | 16 | |
27 | ... | 90 | 22.4 | 275 | 68.6 | 8 | |
35 | ... | 41 | 23.3 | 110 | 62.5 | 15 |
white_per | male_num | male_per | female_num | female_per | |
---|---|---|---|---|---|
6 | 2.1 | 97.0 | 51.3 | 92.0 | 48.7 |
13 | 8.5 | 147.0 | 44.8 | 181.0 | 55.2 |
20 | 2.6 | 330.0 | 52.7 | 296.0 | 47.3 |
27 | 2.0 | 204.0 | 50.9 | 197.0 | 49.1 |
35 | 8.5 | 97.0 | 55.1 | 79.0 | 44.9 |
graduation
Demographic | DBN | School Name | Cohort | \ | |
---|---|---|---|---|---|
3 | Total Cohort | 01M292 | HENRY STREET SCHOOL FOR INTERNATIONAL | 2006 | |
10 | Total Cohort | 01M448 | UNIVERSITY NEIGHBORHOOD HIGH SCHOOL | 2006 | |
17 | Total Cohort | 01M450 | EAST SIDE COMMUNITY SCHOOL | 2006 | |
24 | Total Cohort | 01M509 | MARTA VALLE HIGH SCHOOL | 2006 | |
31 | Total Cohort | 01M515 | LOWER EAST SIDE PREPARATORY HIGH SCHO | 2006 |
Total Cohort | Total Grads — n | Total Grads — % of cohort | Total Regents — n | \ | |
---|---|---|---|---|---|
3 | 78 | 43 | 55.1% | 36 | |
10 | 124 | 53 | 42.7% | 42 | |
17 | 90 | 70 | 77.8% | 67 | |
24 | 84 | 47 | 56% | 40 | |
31 | 193 | 105 | 54.4% | 91 |
Total Regents — % of cohort | Total Regents — % of grads | ... | Regents w/o Advanced — n | \ | |
---|---|---|---|---|---|
3 | 46.2% | 83.7% | ... | 36 | |
10 | 33.9% | 79.2% | ... | 34 | |
17 | 74.400000000000006% | 95.7% | ... | 67 | |
24 | 47.6% | 85.1% | ... | 23 | |
31 | 47.2% | 86.7% | ... | 22 |
Regents w/o Advanced — % of cohort | Regents w/o Advanced — % of grads | \ | |
---|---|---|---|
3 | 46.2% | 83.7% | |
10 | 27.4% | 64.2% | |
17 | 74.400000000000006% | 95.7% | |
24 | 27.4% | 48.9% | |
31 | 11.4% | 21% |
Local — n | Local — % of cohort | Local — % of grads | Still Enrolled — n | \ | |
---|---|---|---|---|---|
3 | 7 | 9% | 16.3% | 16 | |
10 | 11 | 8.9% | 20.8% | 46 | |
17 | 3 | 3.3% | 4.3% | 15 | |
24 | 7 | 8.300000000000001% | 14.9% | 25 | |
31 | 14 | 7.3% | 13.3% | 53 |
Still Enrolled — % of cohort | Dropped Out — n | Dropped Out — % of cohort | |
---|---|---|---|
3 | 20.5% | 11 | 14.1% |
10 | 37.1% | 20 | 16.100000000000001% |
17 | 16.7% | 5 | 5.6% |
24 | 29.8% | 5 | 6% |
31 | 27.5% | 35 | 18.100000000000001% |
hs_directory
dbn | school_name | boro | \ | |
---|---|---|---|---|
0 | 17K548 | Brooklyn School for Music & Theatre | Brooklyn | |
1 | 09X543 | High School for Violin and Dance | Bronx | |
2 | 09X327 | Comprehensive Model School Project M.S. 327 | Bronx | |
3 | 02M280 | Manhattan Early College School for Advertising | Manhattan | |
4 | 28Q680 | Queens Gateway to Health Sciences Secondary Sc... | Queens |
building_code | phone_number | fax_number | grade_span_min | grade_span_max | \ | |
---|---|---|---|---|---|---|
0 | K440 | 718-230-6250 | 718-230-6262 | 9 | 12 | |
1 | X400 | 718-842-0687 | 718-589-9849 | 9 | 12 | |
2 | X240 | 718-294-8111 | 718-294-8109 | 6 | 12 | |
3 | M520 | 718-935-3477 | NaN | 9 | 10 | |
4 | Q695 | 718-969-3155 | 718-969-3552 | 6 | 12 |
expgrade_span_min | expgrade_span_max | ... | priority05 | priority06 | priority07 | priority08 | \ | |
---|---|---|---|---|---|---|---|---|
0 | NaN | NaN | ... | NaN | NaN | NaN | NaN | |
1 | NaN | NaN | ... | NaN | NaN | NaN | NaN | |
2 | NaN | NaN | ... | Then to New York City residents | NaN | NaN | NaN | |
3 | 9 | 14.0 | ... | NaN | NaN | NaN | NaN | |
4 | NaN | NaN | ... | NaN | NaN | NaN | NaN |
priority09 | priority10 | Location 1 | \ | |
---|---|---|---|---|
0 | NaN | NaN | 883 Classon Avenue\nBrooklyn, NY 11225\n(40.67... | |
1 | NaN | NaN | 1110 Boston Road\nBronx, NY 10456\n(40.8276026... | |
2 | NaN | NaN | 1501 Jerome Avenue\nBronx, NY 10452\n(40.84241... | |
3 | NaN | NaN | 411 Pearl Street\nNew York, NY 10038\n(40.7106... | |
4 | NaN | NaN | 160-20 Goethals Avenue\nJamaica, NY 11432\n(40... |
DBN | lat | lon | |
---|---|---|---|
0 | 17K548 | 40.670299 | -73.961648 |
1 | 09X543 | 40.827603 | -73.904475 |
2 | 09X327 | 40.842414 | -73.916162 |
3 | 02M280 | 40.710679 | -74.000807 |
4 | 28Q680 | 40.718810 | -73.806500 |
После всей подготовки наконец, мы можем объединить все датасеты по столбцу DBN. В итоге у нас получится датасет с сотнями столбцов, из всех исходных. При объединении важно отметить, что в некоторых датасетах нет тех школ, что есть в датасете sat_results. Чтобы это обойти, нам надо объединять датасеты через outer join, тогда мы не потеряем данные. В реальном анализе отсутствие данных — обычное дело. Продемонстрировать возможность исследовать и справляться с таким отсутствием — важная часть портфолио.
Про разные типы джоинов можно почитать здесь.
В коде ниже мы:
In [75]:
flat_data_names = [k for k,v in data.items()]
flat_data = [data[k] for k in flat_data_names]
full = flat_data[0]
for i, f in enumerate(flat_data[1:]):
name = flat_data_names[i+1]
print(name)
print(len(f["DBN"]) - len(f["DBN"].unique()))
join_type = "inner"
if name in ["sat_results", "ap_2010", "graduation"]:
join_type = "outer"
if name not in ["math_test_results"]:
full = full.merge(f, on="DBN", how=join_type)
full.shape
survey
0
ap_2010
1
sat_results
0
class_size
0
demographics
0
graduation
0
hs_directory
0
Out[75]:
(374, 174)
Теперь, когда у нас есть наш полный датафрейм full, у нас есть практически вся информация для нашего анализа. Хотя ещё есть недостающие части. Мы можем захотеть скорреллировать оценки теста по углубленной программе с оценками ЕГЭ, но сначала надо будет преобразовывать эти столбцы к числам, а потом заполнить все пропущенные значения:
In [76]:
cols = ['AP Test Takers ', 'Total Exams Taken', 'Number of Exams with scores 3 4 or 5']
for col in cols:
full[col] = full[col].convert_objects(convert_numeric=True)
full[cols] = full[cols].fillna(value=0)
Далее, надо посчитать столбец school_dist, который обозначает школьный округ. Он позволит нам сравнить школьные округа и рисовать окружную статистику с использованием карт округов, которые мы скачали:
In [77]:
full["school_dist"] = full["DBN"].apply(lambda x: x[:2])
Наконец, нам надо заполнить все пропущенные значения в full средним значением из колонки, чтобы посчитать корреляции
In [79]:
full = full.fillna(full.mean())
Хороший способ поисследовать датасет и посмотреть, как столбцы связаны с тем, чем нужно — это посчитать корреляции. Это покажет, какие столбцы связаны с интересующим столбцом. Это можно сделать методом corr в датафреймах Pandas. Чем ближе корреляция к 0 — тем слабее связь. Чем ближе к 1 — тем сильнее прямая связь. Чем ближе к -1 — тем сильнее обратная связь:
In [80]:
full.corr()['sat_score']
Out[80]:
Year NaN
Number Tested 8.127817e-02
rr_s 8.484298e-02
rr_t -6.604290e-02
rr_p 3.432778e-02
N_s 1.399443e-01
N_t 9.654314e-03
N_p 1.397405e-01
saf_p_11 1.050653e-01
com_p_11 2.107343e-02
eng_p_11 5.094925e-02
aca_p_11 5.822715e-02
saf_t_11 1.206710e-01
com_t_11 3.875666e-02
eng_t_10 NaN
aca_t_11 5.250357e-02
saf_s_11 1.054050e-01
com_s_11 4.576521e-02
eng_s_11 6.303699e-02
aca_s_11 8.015700e-02
saf_tot_11 1.266955e-01
com_tot_11 4.340710e-02
eng_tot_11 5.028588e-02
aca_tot_11 7.229584e-02
AP Test Takers 5.687940e-01
Total Exams Taken 5.585421e-01
Number of Exams with scores 3 4 or 5 5.619043e-01
SAT Critical Reading Avg. Score 9.868201e-01
SAT Math Avg. Score 9.726430e-01
SAT Writing Avg. Score 9.877708e-01
...
SIZE OF SMALLEST CLASS 2.440690e-01
SIZE OF LARGEST CLASS 3.052551e-01
SCHOOLWIDE PUPIL-TEACHER RATIO NaN
schoolyear NaN
frl_percent -7.018217e-01
total_enrollment 3.668201e-01
ell_num -1.535745e-01
ell_percent -3.981643e-01
sped_num 3.486852e-02
sped_percent -4.413665e-01
asian_num 4.748801e-01
asian_per 5.686267e-01
black_num 2.788331e-02
black_per -2.827907e-01
hispanic_num 2.568811e-02
hispanic_per -3.926373e-01
white_num 4.490835e-01
white_per 6.100860e-01
male_num 3.245320e-01
male_per -1.101484e-01
female_num 3.876979e-01
female_per 1.101928e-01
Total Cohort 3.244785e-01
grade_span_max -2.495359e-17
expgrade_span_max NaN
zip -6.312962e-02
total_students 4.066081e-01
number_programs 1.166234e-01
lat -1.198662e-01
lon -1.315241e-01
Name: sat_score, dtype: float64
Эти данные дают нам ряд подсказок, которые надо будет проработать:
Каждый пункт — потенциальное место для исследования и истории на основе данных.
На всякий случай напомню, что корреляция (и ковариация) показывают только меру линейной зависимости. Если связь есть, но не линейная а, скажем, квадратичная — корреляция ничего толкового не покажет.
Ну и, конечно же, корреляционная связь ни в коем роде не указывает на причину и следствие. Просто, что две величины склонны изменяться пропорционально. Ниже как раз пример с поиском действительной закономерности и разобран.
Прежде, чем погружаться в исследование данных, надо бы определить контекст, как для нас самих, так и для тех, кто будет читать потом наш анализ. Хороший способ — исследовательские графики или карты. В нашем случае, мы нарисуем на карте месторасположение наших школ, что поможет читающим понять исследуемую нами проблему.
В коде ниже мы:
In [82]:
import folium
from folium import plugins
schools_map = folium.Map(location=[full['lat'].mean(), full['lon'].mean()], zoom_start=10)
marker_cluster = folium.MarkerCluster().add_to(schools_map)
for name, row in full.iterrows():
folium.Marker([row["lat"], row["lon"]], popup="{0}: {1}".format(row["DBN"], row["school_name"])).add_to(marker_cluster)
schools_map.create_map('schools.html')
schools_map
Out[82]:
Карта в целом помогает, но сложно понять, где же больше всего в Нью-Йорке школ. Сделаем вместо этого тепловую карту:
In [84]:
schools_heatmap = folium.Map(location=[full['lat'].mean(), full['lon'].mean()], zoom_start=10)
schools_heatmap.add_children(plugins.HeatMap([[row["lat"], row["lon"]] for name, row in full.iterrows()]))
schools_heatmap.save("heatmap.html")
schools_heatmap
Out[84]:
Тепловые карты хороши для отображения градиентов, но хочется что-то более структурированное, чтобы отразить разницу в оценках ЕГЭ по городу. Школьные округа подходят для отображения этой информации, т.к. у каждого округа своя администрация. В Нью-Йорке несколько дюжин школьных округов, и каждый занимает небольшую площадь.
Мы можем посчитать оценки за ЕГЭ по округу и нанести их на карту. В коде ниже мы:
In [ ]:
district_data = full.groupby("school_dist").agg(np.mean)
district_data.reset_index(inplace=True)
district_data["school_dist"] = district_data["school_dist"].apply(lambda x: str(int(x))
Теперь мы можем нарисовать средний балл ЕГЭ по каждому школьному округу. Для этого прочитаем данные в формате GeoJSON, чтобы определить форму каждого округа, потом сопоставим каждую форму с оценкой с помощью столбца school_dist, и, наконец, нарисуем график.
In [85]:
def show_district_map(col):
geo_path = 'schools/districts.geojson'
districts = folium.Map(location=[full['lat'].mean(), full['lon'].mean()], zoom_start=10)
districts.geo_json(
geo_path=geo_path,
data=district_data,
columns=['school_dist', col],
key_on='feature.properties.school_dist',
fill_color='YlGn',
fill_opacity=0.7,
line_opacity=0.2,
)
districts.save("districts.html")
return districts
show_district_map("sat_score")
Out[85]:
У нас теперь есть контекст на графике со школами, и оценки ЕГЭ по округам; люди, просматривающие наш анализ лучше поймут контекст датасета. Установив декорации, можем теперь приступить к исследованию тех аспектов, что мы уже установили во время поиска корреляций. Первый аспект — исследовать связь между числом студентов, посещающих школу, и оценками ЕГЭ.
Мы можем исследовать это через диаграмму рассеяния, которая сравнит наполняемость с оценками по всем школам:
In [87]:
%matplotlib inline
full.plot.scatter(x='total_enrollment', y='sat_score')
Out[87]:
<matplotlib.axes._subplots.AxesSubplot at 0x10fe79978>
Видим, что есть кластер слева внизу с низкой наполняемостью и оценками. За исключением него, есть слабая позитивная корреляция между оценками ЕГЭ и общей наполняемостью. График наших корреляций может выявить неожиданные закономерности.
Мы можем исследовать этот момент глубже, узнав названия школ с низкой наполняемостью и оценками:
In [88]:
full[(full["total_enrollment"] < 1000) & (full["sat_score"] < 1000)]["School Name"]
Out[88]:
34 INTERNATIONAL SCHOOL FOR LIBERAL ARTS
143 NaN
148 KINGSBRIDGE INTERNATIONAL HIGH SCHOOL
203 MULTICULTURAL HIGH SCHOOL
294 INTERNATIONAL COMMUNITY HIGH SCHOOL
304 BRONX INTERNATIONAL HIGH SCHOOL
314 NaN
317 HIGH SCHOOL OF WORLD CULTURES
320 BROOKLYN INTERNATIONAL HIGH SCHOOL
329 INTERNATIONAL HIGH SCHOOL AT PROSPECT
331 IT TAKES A VILLAGE ACADEMY
351 PAN AMERICAN INTERNATIONAL HIGH SCHOO
Name: School Name, dtype: object
Поиск в Гугле показывает, что большая часть этих школ для студентов, изучающих английский, и, как итог, с низкой наполняемостью. Это исследование показало, что не наполняемость в целом коррелирует с оценками ЕГЭ — коррелирует то, изучают ли ученики в школе английский как второй язык, или нет.
Теперь мы знаем, что процент изучающих английский в школе связан с более низкими оценками ЕГЭ, и можем исследовать эту связь. Столбец ell_percent - это процент изучающих английский в каждой школе. Можем построить диаграмму рассеяния по этой связи:
In [89]:
full.plot.scatter(x='ell_percent', y='sat_score')
Out[89]:
<matplotlib.axes._subplots.AxesSubplot at 0x10fe824e0>
Похоже, есть группа школ с высоким ell_percentage и с низким средним баллом ЕГЭ. Можем это исследовать на уровне округов, определяя процент изучающих английский в каждом округе и смотря, как он связан с оценкой по округу:
In [90]:
show_district_map("ell_percent")
Out[90]:
Как мы видим по двум картам округов, округа с более низким процентом англоизучающих имеют тенденцию к более высоким оценкам ЕГЭ, и наоборот.
Логично было бы предположить, что результаты опросов учеников, родителей и учителей будут сильно коррелировать с оценками ЕГЭ. Разумно, что школы с более высокими учебными ожиданиями имеют тенденцию к более высоким баллам ЕГЭ. Для проверки этой теории построим график оценок ЕГЭ и различных показателей из опросов:
In [91]:
full.corr()["sat_score"][["rr_s", "rr_t", "rr_p", "N_s", "N_t", "N_p", "saf_tot_11", "com_tot_11", "aca_tot_11", "eng_tot_11"]].plot.bar()
Out[91]:
<matplotlib.axes._subplots.AxesSubplot at 0x114652400>
На удивление, больше всего коррелируют N_p и N_s, число участвовавших в опросе родителей и учеников соответственно. Оба сильно коррелируют с общей наполняемостью, так что скорее всего они испытывают влияние и ell_learners. Ещё одна сильно коррелирующая метрика — saf_t_11. Это то, как оценивают безопасность школы ученики, родители и учителя. Логично, что, чем безопаснее школа — тем более комфортно ученикам там учиться. Хотя, больше ни один фактор, типа вовлечённости, общения и академических ожиданий, не коррелирует с оценками ЕГЭ. Это может объясняться тем, что в Нью-Йорке задают неправильные вопросы в опросах, или что предполагают влияние на эти оценки не тех факторов (если их цель — улучшить оценки ЕГЭ, то такого быть не должно).
Ещё один аспект для исследования включает в себя расы и оценки ЕГЭ. Там была большая корреляция, и её график поможет нам понять, что происходит:
In [92]:
full.corr()["sat_score"][["white_per", "asian_per", "black_per", "hispanic_per"]].plot.bar()
Out[92]:
<matplotlib.axes._subplots.AxesSubplot at 0x108166ba8>
Похоже, высокий процент белых и азиатских учеников коррелирует с более высокими оценками ЕГЭ, но более высокий процент черных и испанцев коррелирует с более низкими оценками. Для испанских студентов это может быть следствием факта, что там много недавних иммигрантов, изучающих английский. Нанесём на карту процент испаноязычных по округу, чтобы увидеть корреляцию глазами:
In [93]:
show_district_map("hispanic_per")
Out[93]:
Похоже, какая-то корреляция с процентом англоизучающих есть, но надо копнуть глубже в этом и других направлениях расовых различий и оценок ЕГЭ.
Последний аспект для исследования — связь между полом и оценками. Мы уже заметили, что более высокий процент женщин в школе имеет тенденцию к корреляции с более высокими баллами ЕГЭ. Мы можем этого отразить на столбчатой диаграмме:
In [94]:
full.corr()["sat_score"][["male_per", "female_per"]].plot.bar()
Out[94]:
<matplotlib.axes._subplots.AxesSubplot at 0x10774d0f0>
Чтобы лучше разобраться в корреляции, сделаем диаграмму рассеяния между female_per и sat_score:
In [95]:
full.plot.scatter(x='female_per', y='sat_score')
Out[95]:
<matplotlib.axes._subplots.AxesSubplot at 0x104715160>
Похоже, есть кластер школ с высоким процентом женщин и очень высокими оценками ЕГЭ (справа вверху). Мы можем узнать названия школ этого кластера:
In [96]:
full[(full["female_per"] > 65) & (full["sat_score"] > 1400)]["School Name"]
Out[96]:
3 PROFESSIONAL PERFORMING ARTS HIGH SCH
92 ELEANOR ROOSEVELT HIGH SCHOOL
100 TALENT UNLIMITED HIGH SCHOOL
111 FIORELLO H. LAGUARDIA HIGH SCHOOL OF
229 TOWNSEND HARRIS HIGH SCHOOL
250 FRANK SINATRA SCHOOL OF THE ARTS HIGH SCHOOL
265 BARD HIGH SCHOOL EARLY COLLEGE
Name: School Name, dtype: object
Гугл нам объясняет, что это элитные школы, специализирующиеся на изобразительном искусстве. В этих школах больше процент женщин и выше оценки. Отсюда, скорее всего, корреляция между высоким процентом женщин и высокими оценками, и, наоборот, корреляция между высоким процентом мужчин и низкими оценками ЕГЭ.
Замечательная первая диаграмма наглядно демонстрирует нам, что в сумме процент мужчин и женщин должен быть в каждой школе 100 (ну или близок к нему).
До сего момента мы рассматривали демографические аспекты. Один аспект в данных, на который надо обратить внимание — это связь между большим числом студентов, сдавших углубленную программу, и высокими оценками ЕГЭ. Логично, что они должны коррелировать, поскольку ученики с более высокими желаниями в учёбе должны лучше сдавать ЕГЭ.
In [98]:
full["ap_avg"] = full["AP Test Takers "] / full["total_enrollment"]
full.plot.scatter(x='ap_avg', y='sat_score')
Out[98]:
<matplotlib.axes._subplots.AxesSubplot at 0x11463a908>
Похоже, между этими вещами сильная корреляция. Справа вверху есть интересный кластер школ с высокими оценками и высокой долей учеников, сдававших экзамен по УП:
In [99]:
full[(full["ap_avg"] > .3) & (full["sat_score"] > 1700)]["School Name"]
Out[99]:
92 ELEANOR ROOSEVELT HIGH SCHOOL
98 STUYVESANT HIGH SCHOOL
157 BRONX HIGH SCHOOL OF SCIENCE
161 HIGH SCHOOL OF AMERICAN STUDIES AT LE
176 BROOKLYN TECHNICAL HIGH SCHOOL
229 TOWNSEND HARRIS HIGH SCHOOL
243 QUEENS HIGH SCHOOL FOR THE SCIENCES A
260 STATEN ISLAND TECHNICAL HIGH SCHOOL
Name: School Name, dtype: object
Гугл показывает, что это достаточно требовательные школы, куда нужно сдать экзамен, чтобы попасть. Логично, что в этих школах много кто берёт углубленную программу.
В data science история никогда по-настоящему не заканчивается. Раскрывая анализ остальным, вы позволяете им расширять и формировать его в интересном им направлении. К примеру, в этом посте есть аспекты, которые мы исследовали не полностью, и в которые можно погрузиться глубже.
Один из отличных способов начать рассказывать истории с помощью данных — попытаться расширить или повторить чужой анализ. Если решите двигаться в этом направлении — можете расширять анализ этого поста и попробовать найти что-нибудь ещё. Если так — оставьте мне комментарий, чтобы я посмотрел.
Не мне, конечно, Вику. :)
Если вы добрались до сюда — у вас уже должно быть хорошее понимание того, как рассказывать историю через данные и как сделать первую работу для портфолио.
В Dataquest, у нас сделаны интерактивные обучающие проекты, чтобы вы начали делать своё портфолио для демонстрации его работодателям. Если интересно — можете подписаться и пройти первый модуль бесплатно.