Здравствуйте, друзья, сегодня я представляю вам долгожданный (я полагаю) вывод из моей работы с данными о вакансиях, собранными с сайта объявлений о вакансиях Indeed.com. Что касается более ранних сегментов этой истории, вы можете прочитать о том, как я изначально собирал эти данные, а также о начальной очистке данных, которую я выполнил перед этим анализом.

Когда мы в последний раз остановились, я провел начальную очистку моих очищенных данных, так что мой фрейм данных (scrape_data) содержал семь столбцов, отмечая несколько частей информации для каждой публикации:

  1. город вошел при соскобе;
  2. название должности;
  3. название компании;
  4. местоположение, связанное с работой (иногда связанный город, иногда пригород указанного города);
  5. краткий отрывок из резюме вакансии;
  6. ориентировочная годовая зарплата для каждой должности, если таковая имеется; а также
  7. исходный период заработной платы, указанный для каждой разноски, если таковой имеется (например, если зарплата была первоначально разнесена как почасовая, эта цифра была преобразована в годовую оценку в столбце зарплаты, но исходный период заработной платы здесь был сохранен).

Ниже приведен быстрый взгляд на хвост нашего фрейма данных.

Хотя это и не видно на изображении выше, подавляющее большинство извлеченных объявлений о вакансиях вообще не содержало никакой информации о зарплате. Из 6399 уникальных объявлений о вакансиях только 449 из них (7%) содержали информацию о зарплате. В идеале мы могли бы проанализировать информацию о тех объявлениях, которые действительно содержат информацию о зарплате, чтобы построить модель, которая может информировать нас об относительных перспективах заработной платы на тех должностях, которые вообще не содержат информации о зарплате.

Хотя мы можем сделать это разными способами, для простоты, здесь мы построим модель, которая пытается предсказать, имеет ли данное объявление о вакансии зарплата выше или ниже медианного значения заработной платы всех уволенных вакансий. Таким образом, при рассмотрении будущих заявлений о приеме на работу мы могли бы сосредоточить свои усилия на тех должностях, которые, вероятно, будут приносить желаемую заработную плату.

Итак, отсюда у нас есть несколько целей при анализе наших извлеченных данных:

  1. Определите среднюю величину заработной платы тех должностей, которые содержат существующую информацию о заработной плате.
  2. Изучите объявления о вакансиях, содержащие данные о зарплате, чтобы определить те характеристики, которые больше всего связаны с зарплатой выше или ниже средней. (На этом этапе мы будем преимущественно использовать обработку естественного языка.)
  3. Создайте прогностическую модель, оценивающую перспективы заработной платы, и примените эту модель к тем объявлениям о вакансиях, которые не содержат информации о заработной плате.

Первоначальное исследование и подготовка данных

Для начала мы импортируем несколько базовых библиотек Python, которые помогут как в анализе, так и в визуализации данных. Мы будем делать большую часть нашей первоначальной работы в Pandas, используя NumPy. Мы также импортируем Seaborn и Matplotlib, чтобы облегчить визуализацию.

import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
# display plots in the notebook
%matplotlib inline
# increase default figure and font sizes for easier viewing
plt.rcParams[‘figure.figsize’] = (8, 6)
plt.rcParams[‘font.size’] = 17

Конечная цель этого анализа - предсказать бинарную переменную - будет ли зарплата выше или ниже медианного значения. Итак, нам сначала нужно вычислить среднюю зарплату, а затем создать новую двоичную переменную, которая равна 1,0, когда зарплата высокая (выше медианы), и 0,0, когда она низкая (ниже медианы).

#what is the median salary? (calculate mean salary, too, for fun)
print("salary median: " + str(scrape_data["salary"].median()))
print("salary mean: " + str(scrape_data["salary"].mean()))
#creating new target variable "above_med_sal" where 0 == below or equal to the median, and 1 == above the median; any postings without salary information will have NaN values for this variable
scrape_data["above_med_sal"] = np.nan
scrape_data.ix[scrape_data["salary"] > scrape_data["salary"].median(), "above_med_sal"] = 1
scrape_data.ix[scrape_data["salary"] <= scrape_data["salary"].median(), "above_med_sal"] = 0
scrape_data.tail()

Мы видим, что наша средняя заработная плата составляет 87 915 долларов, а средняя зарплата - 98 754 доллара. По этим цифрам мы можем сказать, что наши данные явно искажены, но график данных о заработной плате может дать нам лучшее приближение того, как это выглядит. Построим график распределения доступных заработных плат и добавим несколько вертикальных линий, указывающих средние и средние значения заработной платы.

#plotting salary distribution, with vertical lines to represent the mean and median salary
sal_plot = scrape_data[scrape_data["salary"].notnull()]
ax = sns.distplot(sal_plot["salary"])
ax.axvline(sal_plot["salary"].median(), lw=2.5, ls='dashed', color='black')
ax.axvline(sal_plot["salary"].mean(), lw=2.5, ls='dashed', color='red')
sns.plt.title('Indeed Salary Distribution with Median (black) and Mean (red)')

Если посмотреть на общий диапазон заработной платы, то наши доступные рабочие места обеспечивают от 22 100 до 270 400 долларов в год, причем наиболее часто встречающаяся зарплата составляет 50 000 долларов.

Теперь, когда у нас есть представление о том, как распределяются наши данные, мы можем приступить к более глубокому анализу данных, чтобы изучить те характеристики, которые наиболее характерны для предполагаемой годовой заработной платы.

Подходя к процессу построения модели, мы будем работать только с объявлениями о вакансиях, содержащими информацию о заработной плате. Мы делаем это, потому что зарплата на работе выше или ниже медианного значения является нашей целью, представляющей интерес (то есть средством, с помощью которого мы определяем эффективность нашей модели). На данный момент мы будем хранить эти объявления без каких-либо сведений о зарплате для наших окончательных прогнозов.

#We'll only be training our model on jobs that have salary data, so separate out salary jobs
salary_data = scrape_data[scrape_data.above_med_sal.notnull()]
#splitting out our predictor variables from the salary data
sal_X = salary_data.iloc[:,0:len(salary_data.columns)-1]
#sal_y will be above_med_sal, creating as a dataframe
sal_y = pd.DataFrame(salary_data["above_med_sal"])

Теперь еще одна передовая практика при построении моделей - провести разделение на поезд и тест. Это эффективно сегментирует наши данные на отдельные группы, одна для построения нашей модели (набор поездов), а другая для тестирования и проверки нашей модели (набор тестов).

Набор тестов помогает проверить нашу модель, служа источником данных, которые вообще не использовались при построении нашей модели. Если модель хорошо работает с данными, которых она никогда раньше не видела, мы можем быть достаточно уверены в способности нашей модели хорошо работать с другими аналогичными данными, которые мы могли бы собрать в будущем.

Мы импортируем модуль обучения и тестирования из scikit-learn и применим его к нашим данным о заработной плате. Здесь мы сделаем разделение 80:20, чтобы 80% наших данных о заработной плате использовалось для обучения модели, а оставшиеся 20% - для тестирования и проверки. Мы хотим стратифицировать наши данные по переменной y (above_med_sal), чтобы убедиться, что соотношение заработной платы выше и ниже медианы сохраняется во всех наборах поездов и тестов. Мы установим здесь случайное состояние, чтобы гарантировать, что наша работа может быть воспроизведена, если мы повторно запустим наш анализ в будущем. Здесь я использовал генератор случайных чисел Google, чтобы выбрать random_state равное 74.

Я сбросил индекс каждого из сгенерированных наборов для удобства использования.

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(sal_X, sal_y, test_size=0.2, stratify=sal_y, random_state=74)
X_train.reset_index(drop=True, inplace=True)
X_test.reset_index(drop=True, inplace=True)
y_train.reset_index(drop=True, inplace=True)
y_test.reset_index(drop=True, inplace=True)

Изучение характеристик работы с помощью обработки естественного языка

Как вы, несомненно, заметили из приведенных выше снимков экрана фрейма данных, почти вся информация, которую мы имеем о наших объявлениях о вакансиях, носит качественный характер, то есть повествовательная. Чтобы лучше изучить эту информацию, мы воспользуемся обработкой естественного языка (NLP), чтобы изучить частоту употребления слов и фраз (также известных как n-граммы) в каждой из наших переменных. Отсюда мы определим те n-граммы, которые сильно связаны с объявлениями о вакансиях с зарплатой выше или ниже средней.

Предположительно, n-граммы, которые сильно связаны с объявлениями о заработной плате выше или ниже медианы, должны точно предсказывать статус средней заработной платы на рабочих местах, которые мы занимали вне нашей обучающей выборки. Например, если наш обучающий набор показывает, что такой термин, как «специалист по данным», часто встречается в объявлениях о вакансиях с зарплатой выше медианы и редко в объявлениях о вакансиях с зарплатой ниже медианы, мы можем предположить, что объявления о вакансиях в нашем тестовом наборе содержат термин « специалист по данным »с большей вероятностью будет иметь зарплату выше средней. Таким образом, мы могли бы разработать некоторые новые функции на основе результатов нашего НЛП для использования в нашей модели прогнозирования.

Для каждой функции в X_train я использовал векторизатор подсчета из scikit-learn, чтобы исследовать частоты n-граммов, связанных с должностями с зарплатой выше и ниже медианы. Затем я построил коэффициенты, проверяющие относительный вид каждого n-грамма в положениях с зарплатой выше и ниже средней. Чем выше коэффициент выше 1,0, тем сильнее связь n-грамма с заработной платой выше медианы; чем ниже коэффициент ниже 1,0, тем сильнее связь n-грамма с заработной платой ниже медианы. Коэффициенты, близкие к 1,0, указывают на то, что n-граммы относительно часто появлялись на должностях выше медианы, чем на должностях ниже медианы, что указывает на небольшую прогностическую способность.

Я буду рассматривать этот процесс шаг за шагом, используя в качестве примера анализ отдельной функции.

Во-первых, давайте инициализируем векторизатор счетчика.

Для этого анализа я установил свой n-граммовый диапазон от 1 до 3, что означает, что наш анализ будет исследовать последовательные строки от одного до трех слов. Меня также интересовали только слова, которые появлялись по крайней мере в 5% сообщений, чтобы вырезать редко представленные слова, которые не имели бы полезной различающей способности. Наконец, я настроил свой векторизатор на игнорирование общеупотребительных английских слов, таких как «the», «is», «a» и т. Д. Через stop_words = ‘english’.

#initializing count vectorizer to examine text-based data for logistic regression. 
from sklearn.feature_extraction.text import CountVectorizer
cvec = CountVectorizer(stop_words='english',ngram_range=(1, 3), min_df = .05)

Теперь я продемонстрирую процесс проверки названий должностей из набора. Однако помните, что этот процесс применялся к каждой из переменных в обучающем наборе.

#fitting count vectorizer to job title
cvec.fit(X_train["job_title"])
#transforming job_title data into job_train
job_train = pd.DataFrame(cvec.transform(X_train["job_title"]).todense(),
                       columns=cvec.get_feature_names())

Чтобы изучить, как n-граммы из названий должностей связаны со средним статусом заработной платы, мы объединим наши данные о зарплате в фрейм данных job_train.

word_observe = pd.concat([job_train, y_train], axis=1)
word_observe.head()

Теперь мы кратко рассмотрим частоту появления n-грамм, связанных с должностями с зарплатой ниже медианы (above_med_sal == 0) и должностями с зарплатой выше медианы (above_med_sal == 1).

#Observing most common job title n-grams for both above and below median salaries
for i in xrange(2):
    word_count = word_observe[word_observe["above_med_sal"]==i].sum(axis=0)
    print i, "most common words"
    cw = word_count.sort_values(ascending = False).head(20)
    print cw
    print

Хотя мы можем получить приблизительное представление о некоторых терминах (например, «исследование» довольно часто появляется на должностях ниже медианы и довольно редко на должностях выше медианы), возможность увидеть соотношение появления этих терминов сделает нашу работу Намного легче определить относительную дискриминационную силу наших n-граммов.

#creating separate lists of the top appearing words so that these can be combined and compared to create ratios of appearances in above-median vs. below-median salaries
word_count_below = word_observe[word_observe["above_med_sal"]==0].sum(axis=0)
word_count_above = word_observe[word_observe["above_med_sal"]==1].sum(axis=0)
#creating as dataframe, using method ".T" to transpose columns with index.
word_count_compare = pd.DataFrame([word_count_below, word_count_above]).T
#creating a "ratio" column to determine frequency of words associated with above median jobs vs. below median jobs
word_count_compare["above_below_ratio"] = word_count_compare[1]/word_count_compare[0]
word_count_compare.above_below_ratio.sort_values(ascending=False)

Вуаля! Глядя на эти соотношения, мы видим, что такие термины в названии должности, как «специалист по данным», комбинации «машина», «обучение», «инженер» и «данные» - все они имеют мощную предсказательную силу для превышения средней зарплаты. Для сравнения, такие термины в названии должности, как «исследование», «аналитик» и «сотрудник», связаны с заработной платой ниже средней.

Мы можем использовать эти идеи при разработке функций для нашей модели прогнозирования, чтобы определять их появление в объявлениях о вакансиях и соответственно прогнозировать статус средней заработной платы рабочих мест. Например, мы можем использовать термины с коэффициентами выше 3,0 для создания признака «хорошее название должности» и термины с коэффициентами ниже 0,5 для создания характеристики «плохое название должности».

Проведя аналогичный анализ других характеристик извлеченных объявлений о вакансиях, я сделал несколько исследовательских выводов:

Город - городами, в которых заработная плата превышает среднюю, чаще всего ассоциируются с Чикаго, Вашингтоном и Сан-Франциско.

Должность - такие термины, как «специалист по данным», комбинации «машина», «обучение», «инженер» и «данные» - все они обладают мощной предсказательной силой, позволяющей прогнозировать превышение средней зарплаты; Такие термины в названии должности, как «исследование», «аналитик» и «сотрудник», связаны с заработной платой ниже средней.

Название компании. Названия компаний было довольно сложно анализировать, поскольку названия компаний могли включать множество уникальных слов или сокращений, которые не обязательно соответствовали типу выполняемой работы. Работа в (предположительно) консалтинговых фирмах (т. Е. В компаниях, в названии которых есть «партнеры») связана с зарплатой выше медианы, а работа в «университете» связана с зарплатой ниже медианы.

Местоположение. Из-за большого разнообразия местоположений и некоторого совпадения с указанным выше параметром "Город" я решил проверить, совпадает ли местоположение работы с названием связанного города. То есть вакансия размещалась для самого города или его пригорода? Это было сделано для того, чтобы проверить, имеет ли работа в городе, с которым она связана (или за его пределами), какое-либо влияние.

Сводка вакансии - термины резюме вакансии, включая «машинное обучение», «машина», «обучение», «специалист по данным», «наука о данных», «старший», «ведущий специалист», «большие данные». «Аналитика» и «годы» (по-видимому, требующие, по крайней мере, такого многолетнего опыта) - все они связаны с заработной платой выше средней; Сводные термины по должности, включая «исследование», «анализ» и «анализ», связаны с заработной платой ниже средней.

Разработка функций и построение моделей

Теперь, когда мы проанализировали наши данные о вакансиях на предмет релевантной информации, связанной со статусом средней заработной платы, мы можем разработать функции, относящиеся к построению нашей прогнозной модели. Основываясь на выводах, полученных выше, я решил построить модель с восемью бинарными функциями, полученными из каждого объявления о вакансии, чтобы предсказать, будет ли работа предлагать зарплату выше или ниже медианы:

  1. Была ли работа в Чикаго, Вашингтоне или Сан-Франциско?
  2. Содержало ли название должности «хорошие» термины, в том числе «специалист по данным», «машина», «обучение» или «инженер»?
  3. Содержало ли название должности «плохие» термины, включая «исследование», «аналитик» или «сотрудник»?
  4. Включено ли в названии компании «хороший» термин «сотрудники»?
  5. Включено ли в названии компании «плохой» термин «университет»?
  6. Было ли объявление о вакансии расположено в городе, в котором она была размещена, или в близлежащем районе (например, в Сиэтле или Редмонде, штат Вашингтон)?
  7. Содержало ли описание должности «хорошие» термины, включая «машинное обучение», «специалист по данным», «наука о данных», «старший», «ведущий специалист», «большие данные», «аналитика» или «годы»?
  8. Содержало ли описание вакансии «плохие» термины, включая «исследование», «анализ» и «анализ»?

После того, как эти функции были разработаны для каждой публикации, я использовал модель логистической регрессии, чтобы предсказать, будет ли каждое объявление о вакансии обеспечивать зарплату выше или ниже медианы.

Подготовка данных была немного обширной, но ниже вы можете увидеть, как я спроектировал каждую из этих функций для моего набора данных ниже, обычно проверяя, содержат ли переменные строки n-граммы или основы слов, представляющих интерес.

#Creating eight binary variables utilizing insights above to prepare data for logistic regression
#splitting out our predictor variables from the salary data
sal_X = salary_data[["city", "job_title", "company_name", "location", "summary", "og_salary_period"]]
sal_X.reset_index(drop=True, inplace=True)
#sal_y will be above_med_sal, creating as a dataframe, then converting to a 1-D array to allow for easier use below with k-folds, etc.
sal_y = pd.DataFrame(salary_data["above_med_sal"])
sal_y = pd.DataFrame.as_matrix(sal_y).ravel()
#Creating variable for if city is chi, DC, or sf
sal_X["is_chi_dc_sf"] = 0
sal_X.ix[(sal_X["city"].str.contains("chicago") | 
          sal_X["city"].str.contains("washington dc") | 
          sal_X["city"].str.contains("san francisco")), "is_chi_dc_sf"] = 1
#Creating variable for if job title includes "data scientist", combinations of "machine", "learning", "engineer", and "data"
sal_X["good_job_title"] = 0
sal_X.ix[(sal_X["job_title"].str.contains("data scientist") | 
            sal_X["job_title"].str.contains("machine") | 
            sal_X["job_title"].str.contains("learning") |
            sal_X["job_title"].str.contains("engineer") |
            sal_X["job_title"].str.contains("data")), "good_job_title"] = 1
# job_title terms like "research", "analyst" "associate" are all associated with below median income
sal_X["bad_job_title"] = 0
sal_X.ix[(sal_X["job_title"].str.contains("research") | 
            sal_X["job_title"].str.contains("analyst") | 
            sal_X["job_title"].str.contains("associate")), "bad_job_title"] = 1
# working for *presumably* consulting firms e.g., "associates" is associated with above median salaries, while working for "university" is associated with below median salaries.
sal_X["good_company"] = 0
sal_X.ix[(sal_X["company_name"].str.contains("associates") ), "good_company"] = 1
sal_X["bad_company"] = 0
sal_X.ix[(sal_X["company_name"].str.contains("university") ), "bad_company"] = 1
# For location data - I will look at whether or not location matches city name to examine whether a job being in the city it's associated with (or outside of it) has any impact.
sal_X["in_city"] = 0
sal_X.ix[(sal_X["location"].str.contains("new york") |
            sal_X["location"].str.contains("seattle") |
            sal_X["location"].str.contains("san francisco") |
            sal_X["location"].str.contains("houston") |
            sal_X["location"].str.contains("denver") |
            sal_X["location"].str.contains("austin") |
            sal_X["location"].str.contains("washington dc") |
            sal_X["location"].str.contains("miami") |
            sal_X["location"].str.contains("phoenix") |
            sal_X["location"].str.contains("pittsburgh") |
            sal_X["location"].str.contains("portland") |
            sal_X["location"].str.contains("philadelphia") |
            sal_X["location"].str.contains("boulder") |
            sal_X["location"].str.contains("dallas") |
            sal_X["location"].str.contains("los angeles") |
            sal_X["location"].str.contains("atlanta") |
            sal_X["location"].str.contains("chicago")), "in_city"] = 1
# job summary terms including "machine learning", "machine", "learning", "data scientist", "data science", "senior", "lead", "big data", "analytics", "years" (presumably looking for at least so many years) are all associated with above median salaries
sal_X["good_summary"] = 0
sal_X.ix[(sal_X["summary"].str.contains("machine") | 
            sal_X["summary"].str.contains("learning") | 
            sal_X["summary"].str.contains("data scien") |
            sal_X["summary"].str.contains("senior") |
            sal_X["summary"].str.contains("lead") |
            sal_X["summary"].str.contains("big data") |         
            sal_X["summary"].str.contains("analytics") |
            sal_X["summary"].str.contains("years")), "good_summary"] = 1
# "research", "analyze" and "analysis" are all associated with below median salaries
sal_X["bad_summary"] = 0
sal_X.ix[(sal_X["summary"].str.contains("research") | 
            sal_X["summary"].str.contains("analyze") | 
            sal_X["summary"].str.contains("analysis")), "bad_summary"] = 1
#dropping original data columns from sal_X to make analysis easier.
sal_X.drop(["city", "job_title", "company_name", "location", "summary", "og_salary_period"], axis=1, inplace=True)
#converting 1 and 0 values in matrix to float
sal_X = sal_X.astype("float")
sal_X.head()

Теперь у нас осталось восемь двоичных переменных, описывающих информацию о каждой публикации вакансии. Давайте сделаем быструю матрицу корреляции с помощью Seaborn, чтобы проверить, насколько эти переменные взаимосвязаны друг с другом.

# Perform Pearson correlation coefficients using NumPy and Seaborn
feature_correlation_matrix = sal_X.corr()
#Creating heatmap correlation matrix of predictor coefficients.
sns.heatmap(feature_correlation_matrix,
           annot = True,
           linewidths = 0.5)
sns.plt.title('Correlation Heatmap of Eight Binary Variables for Predicting Job Salary')

Учитывая все обстоятельства, между нашими восемью переменными существует довольно хорошее разделение. Good_job_title и good_summary являются наиболее коррелированными из переменных с положительной корреляцией 0,51. Хотя эта взаимосвязь имеет умеренно положительную связь, для наших текущих целей она не вызывает особого беспокойства.

Теперь, когда мы готовы приступить к моделированию наших данных, мы переделаем разделение поезд-тест, чтобы подогнать нашу модель к набору поездов, и проверить ее на нашем тестовом наборе.

X_train, X_test, y_train, y_test = train_test_split(sal_X, sal_y, test_size=0.2, stratify=sal_y, random_state=74)

Теперь, при подгонке нашей логистической регрессии к набору поездов, мы проведем перекрестную проверку, чтобы получить представление о том, насколько последовательно наша модель работает с разными срезами набора поездов. Мы будем импортировать инструменты перекрестной проверки и модель логистической регрессии из scikit-learn, чтобы построить нашу модель.

#now score a logistic regression on X_train and y_train using cross_val_scores
from sklearn.model_selection import cross_val_score
from sklearn import metrics
from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression()
logreg.fit(X_train, y_train)
print "Score:", logreg.score(X_train, y_train)     
scores = cross_val_score(logreg, X_train, y_train, cv=6)
print "Cross-validated scores:", scores

Глядя на начальную оценку для нашего набора поездов, мы можем видеть, что наша модель логистической регрессии была приблизительно на 83% точна при оценке нашей обучающей выборки с диапазоном точности между 76% и 93% при проведении перекрестной проверки. Учитывая все обстоятельства, это довольно хорошо.

Теперь давайте воспользуемся нашей моделью, чтобы спрогнозировать классификацию средней заработной платы в нашем тестовом наборе. Мы импортируем и используем матрицу путаницы и отчет о классификации из scikit-learn, чтобы оценить прогностическую эффективность нашей модели.

# Predicting salaries in X_test given predictors developed with X_train
y_probs = logreg.predict(X_test)   
#generates predicted values of Y_test from X_test based off of training set.
print "Score:", logreg.score(X_test, y_test)
#Create classification and confusion matrix
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
# printing confusion matrix 
print(confusion_matrix(y_test, y_probs,))
#printing classification report
target_names = ['below_med', 'above_med']
print(classification_report(y_test, y_probs, target_names=target_names))

Исходя из этого, мы можем видеть, что модель показала довольно хорошие результаты на тестовом наборе (точность 83%).

Для матрицы неточностей обратите внимание, что строки ссылаются на фактические значения (например, фактические зарплаты ниже медианы; фактические зарплаты выше медианных), а столбцы представляют собой прогнозы (например, прогнозируемые зарплаты ниже медианных; зарплаты выше медианных).

Из этого видно, что пять должностей с зарплатой ниже медианы (11%) в тестовой выборке были неправильно предсказаны как зарплаты выше медианы, а 10 должностей с зарплатой выше медианы (22%) были неправильно предсказаны как должности ниже медианы.

Краткое описание терминологии для отчета о классификации:

оценки точности = истинные положительные результаты / (истинные положительные результаты + ложные положительные результаты) - из всех предсказанных,% правильных

отзыв = истинные положительные результаты / (истинные положительные результаты + ложные отрицательные результаты) - из всех фактических,% правильных

f1-score = взвешенное среднее гармоническое значение точности и запоминания - f1-score достигает своего наилучшего значения при 1 и худшего при 0.

поддержка - это количество фактических наблюдений для каждого класса

Мы видим, что модель работает довольно хорошо во всех отношениях, но она наиболее эффективна для прогнозирования заработной платы выше среднего и определения фактических зарплат ниже среднего.

Применение логистической регрессии для прогнозирования зарплат выше среднего и ниже среднего в объявлениях о вакансиях без информации о зарплате

Наконец, теперь, когда мы построили и проверили нашу модель прогнозирования для известных вакансий с заработной платой, мы можем применить нашу модель для прогнозирования статуса средней заработной платы тех должностей, которые не содержали информации о зарплате при первоначальном извлечении.

Сначала мы выделим посты, не связанные с окладом, из нашего фрейма данных scrape_data в новый фрейм данных pred_data. Мы знаем, что столбец above_med_sal будет пустым для каждой из объявлений о вакансиях, которые мы хотим.

predict_data = scrape_data[scrape_data.above_med_sal.isnull()]

Затем мы обработаем pred_data таким же образом, как мы обрабатывали вышеупомянутые данные о заработной плате, чтобы создать восемь переменных-предикторов. Когда это будет завершено, у нас останется новый фрейм данных pred_X, который выглядит следующим образом:

Теперь мы можем использовать метод прогнозирования логистической регрессии для создания прогнозов средней заработной платы для каждого из наших объявлений о вакансиях и добавить эти прогнозы в виде нового столбца ("salary_prediction") в фрейм данных pred_data следующим образом:

#generating median salary predictions for unsalaried job postings
predict_y = logreg.predict(predict_X)   
predict_data["salary_prediction"] = predict_y
predict_data.head()

При применении модели прогнозирования к оставшимся 5950 объявлениям о вакансиях без перечисления информации о зарплате, 3,128 вакансий (52%), по прогнозам, будут иметь зарплату ниже медианы, а 2 822 (48%) будут выше медианы. В идеальных условиях мы ожидаем, что примерно 50% публикаций будут ниже медианы и примерно 50% - выше медианы. Таким образом, прогнозные результаты обнадеживают в отношении репрезентативной классификации.

Давайте кратко рассмотрим, как наша модель предсказывала зарплаты выше среднего и ниже среднего для каждого из городов, которые мы указали при очистке наших исходных данных.

city_predictions = pd.DataFrame(predict_data[["city", "salary_prediction"]])
city_predictions['median_salary_prediction'] = city_predictions.salary_prediction.map({0:"below_median", 1:"above_median"})
sns.countplot(x="city", hue="median_salary_prediction", data=city_predictions)
plt.ylabel("Number of Job Postings")
plt.xticks(rotation=45)
sns.plt.title('Plot of Salary Predictions by City (8 variable log regression)')
plt.show()

Среди прогнозируемых результатов, Сан-Франциско, по прогнозам, станет крупнейшим центром рабочих мест с зарплатой выше медианы и будет иметь самое высокое прогнозируемое соотношение рабочих мест выше среднего и ниже среднего (2,87 рабочих мест выше среднего на каждую работу ниже медианы). . В Чикаго также прогнозировалось 2,06 рабочих мест выше медианы на каждую работу ниже медианы.

Наименее привлекательными городами для вакансий в области науки о данных были Майами (8,63 рабочих мест ниже медианы на каждую работу выше медианы), Филадельфию (4,16 рабочих мест ниже медианы на каждую работу выше медианы) и Хьюстон (2,79 рабочих мест ниже медианы на каждую работу выше среднего). -средняя работа).

Выводы

Несмотря на то, что эта оценка дала ценную информацию об ожидаемых зарплатах в области науки о данных в различных регионах США, есть несколько предостережений в отношении этих результатов.

Во-первых, предсказательная способность этой модели в некоторой степени ограничена объявлениями о вакансиях из городов, которые были первоначально опрошены. Если объявления о вакансиях будут добавлены из городов, не входящих в первоначальный набор, прогнозирующая способность модели не сможет учесть местоположения, которые не были оценены во время создания модели.

Во-вторых, модель классификации по своей сути предполагает, что распределение заработной платы для должностей с указанным окладом примерно такое же, как и без указанного оклада. Работодатели, перечисляющие более высокооплачиваемые вакансии, могут намеренно не публиковать информацию о зарплате в своих объявлениях, что может привести к появлению вакансий с неопубликованными зарплатами после другого распределения заработной платы, чем те вакансии с объявленными зарплатами.

Наконец, хотя в модели классификации использовались двоичные переменные (то есть с указанием «да» или «нет» для данного элемента), они могут быть скорректированы в будущих оценках, чтобы улавливать градиент информации, что может улучшить предсказательную способность модели. То есть некоторые «хорошие» термины, такие как «специалист по данным» или «машинное обучение», могут более эффективно предсказывать зарплату выше медианной, чем другие «хорошие» термины, такие как «инженер». Определение относительной силы или слабости этих терминов может помочь в дальнейшем различении вероятности получения заработной платы выше или ниже средней в зависимости от их относительного присутствия в любом заданном объявлении о вакансии.

В любом случае, я надеюсь, вам понравился этот обзор моего анализа данных о вакансиях, собранных в Интернете. Интернет изобилует ценными и информативными данными, и, по сути, небо является пределом с точки зрения того, как эта информация может быть собрана и проанализирована для личной выгоды или более широкого воздействия.

Спасибо, что нашли время прочитать!