Наука о данных в реальном мире

Подход к выбору врача с позиций науки о данных

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

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

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

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

Это помеченный набор данных.

Теперь важно четко понимать, что мы прогнозируем. Отзывы пациентов - особенно через поставщика медицинских услуг - позволяют оценить, насколько пациент удовлетворен своим опытом лечения. Это НЕ только показатель качества медицинской помощи, которую они получили от своего врача. Регистрация, гостеприимство, стоимость и кажущаяся тщательность - все это факторы, влияющие на то, насколько пациент может быть удовлетворен своим визитом; Точно так же, как о вашем любимом ресторане, скорее всего, судят по его атмосфере, оформлению, вежливости и стоимости. Но это не обязательно вводит в заблуждение, честно говоря, большинство пациентов не в полной мере квалифицированы, чтобы критиковать медицинские способности своего врача, тогда как пациенты могут воспринимать все эти другие факторы, которые определяют хороший визит - когда новый пациент ищет врача, этот совокупный пациент опыт определенно пригодится.

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

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

Весь вызов api дал нам огромный вложенный словарь, после импорта и сжатия мы смотрим на 82 столбца с характеристиками и 3133 уникальных врача в районе Нью-Йорка.

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

df.dropna(subset=['provider.num_reviews'],inplace=True)
print(df.shape)
(1206, 82)

И теперь у нас есть 1206 точек данных. Примерно 2/3 врачей вообще не имели отзывов через «Оскар». Давайте сейчас составим гистограмму отзывов наших пациентов и посмотрим, как выглядит распределение:

import plotly_express as px
px.histogram(df,
           x='provider.percent_recommend',
           title='Physician review distribution')

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

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

Посмотрим, как основная специальность врача влияет на его рейтинг. С помощью следующего кода мы можем создать диаграмму рассеяния и увидеть статистические различия.

px.scatter(df,
           x="provider.percent_recommend",
           y="provider.relative_cost_to_member",
           color="primary specialty",
           marginal_x="box")

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

Функциональная инженерия

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

Во-первых, давайте посмотрим на все доступные нам функции:

df.columns
['availabilities.ANY', 'availabilities.NEXT_TWO_DAYS',
'availabilities.NEXT_TWO_WEEKS', 'availabilities.THIS_MONTH',
'availabilities.THIS_WEEK', 'availabilityConfidenceLevels.ANY',
'availabilityConfidenceLevels.NEXT_TWO_DAYS',
'availabilityConfidenceLevels.NEXT_TWO_MONTHS',
'availabilityConfidenceLevels.NEXT_TWO_WEEKS',
'availabilityConfidenceLevels.THIS_MONTH',
'availabilityConfidenceLevels.THIS_WEEK',
'availabilityConfidenceLevels.TODAY_OR_TOMORROW',
'provider.INTERNAL_cost_to_member_scores',
'provider.INTERNAL_member_experience_scores',
'provider.INTERNAL_preventive_care_quality_scores',
'provider.INTERNAL_score', 'provider.absolute_cost_to_member',
'provider.affiliations', 'provider.after_hours_indicator',
'provider.biography', 'provider.board_certified',
'provider.can_show_reviews', 'provider.certifications',
'provider.cleveland_clinic_pcp_capacity',
'provider.cost_to_member_bucket', 'provider.dea_info',
'provider.deduped_specialties', 'provider.educations',
'provider.exclusively_pcp', 'provider.first_name',                                          'provider.gender', 'provider.isoDate', 'provider.languages', 'provider.last_edit_source', 'provider.last_edit_time', 'provider.last_name', 'provider.licenses',
'provider.medconds_treated', 'provider.medprocs_treated',
'provider.member_feedback_facet_infos', 'provider.middle_name',
'provider.npi', 'provider.num_reviews', 'provider.offices',
'provider.partner_program', 'provider.patient_demographics',
'provider.pcp', 'provider.percent_0_12', 'provider.percent_13_17',
'provider.percent_18_30', 'provider.percent_31_50',
'provider.percent_51_up', 'provider.percent_female',
'provider.percent_male', 'provider.percent_recommend',
'provider.photo_avatar_id', 'provider.photo_full_body_id',
'provider.preferred_partner', 'provider.preventive_care_quality',
'provider.primary_city', 'provider.primary_state',
'provider.provider_attributes', 'provider.provider_hash',
'provider.provider_id', 'provider.relative_cost_to_member',
'provider.residency_status_primary_specialty',
'provider.residents_attending_physicians_license_number',
'provider.specialist', 'provider.specialties',
'provider.supervising_physician_specialty', 'provider.tin_infos',
'provider.top_provider', 'provider.years_experience']

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

«provider.biography» - действительно огромный ресурс, благодаря целенаправленным усилиям по выделению корней, лемматизации и некоторой комбинации word2vec и набора слов, мы могли бы получить некоторые очень полезные функции.

«provider.languages» идеально подходит для словесного подхода, в конце концов, многие из этих врачей поддерживают тесный контакт с пациентами, и возможность общения с вашими пациентами имеет решающее значение для понимания их потребностей.

‘provider.certifications’ перечисляет сертификаты врача, но, что интересно, при копании в данных, похоже, есть много опечаток. Если об этом сообщили мы сами, мы сможем сделать вывод, будет ли опечатка в ваших сертификатах считаться полезной информацией, в конце концов, если это происходит часто, возможно, это проявление небрежности; но, возможно, эту информацию вводит помощник или агент Оскара, и в этом случае она может быть неуместной - необходимо задать следующие важные вопросы:

  1. Имеет ли значение, что допущена опечатка?
  2. Важно ли, чтобы два человека сделали одну и ту же опечатку?
  3. Стоит ли исправлять опечатки?

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

Посетите ссылку github, чтобы получить более подробную информацию о реализации текстовых функций

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

Посетите ссылку github, чтобы получить более подробную информацию о реализации функции определения местоположения

Используя mapbox, мы можем четко увидеть взаимосвязь между местоположением и нашей целью.

loc_df = data.groupby(['provider.zip']).mean()
loc_df['n']=data.groupby(['provider.zip'])["n_member_feedback_facet_infos"].sum()
px.set_mapbox_access_token(open('.mapbox_token').read())
px.scatter_mapbox(loc_df, lat="LAT", lon="LNG", color='rating', size="n", color_continuous_scale=px.colors.sequential.Viridis)

Хотя наш набор данных ориентирован на высокие оценки, мы определенно можем видеть, что есть части Бруклина и Квинса, которые в среднем имеют более низкие оценки. Сюжет также подчеркивает высокую концентрацию врачей в нижнем Манхэттене.

Я хочу обратить внимание на еще пару функций, в частности, на функции «provider.INTERNAL» и «Providerr.member_feedback_facet_infos». Эти внутренние функции оценки рассчитываются Оскаром, и последняя функция на самом деле является еще одной формой рецензирования - рецензенты могут оставлять конкретные отзывы, такие как «Легко бронировать» и «Хорошая офисная среда». Меня больше всего беспокоит то, что они могут зависеть от оценки пациента, даже если мы уже удалили строки без отзывов. График ковариации ниже, к счастью, показывает, что единственные характеристики, которые сильно коррелированы, - это значки отзывов с количеством отзывов.

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

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

#kfold predictions for each data sample for each model type so we can ensemble
kf = KFold(n_splits=10)
#selection of regression models from sklearn with updated default values
models = [LinearRegression(),
          KNeighborsRegressor(n_neighbors=100),
          BayesianRidge(n_iter=300, tol=0.001),
          DecisionTreeRegressor(),
          RandomForestRegressor(n_estimators=100),
          GradientBoostingRegressor(),
          AdaBoostRegressor()]
#kfold predictions for each data sample for each model type so we can ensemble
kf = KFold(n_splits=10)
#selection of regression models from sklearn with updated default values
models = [KNeighborsRegressor(n_neighbors=100),
          BayesianRidge(n_iter=300, tol=0.001),
          DecisionTreeRegressor(),
          RandomForestRegressor(n_estimators=100),
          GradientBoostingRegressor(),
          AdaBoostRegressor()]
#remove rating from feature set
X,y = data.loc[:,:'LNG'], data.loc[:,'rating']
#initialize scaler
scaler = StandardScaler()
#create k-fold split probability predictions for each model
for train_index,test_index in kf.split(X):
    
    #split data according to random fold
    x_train,y_train,x_test,y_test = X.loc[train_index], y[train_index], X.loc[test_index], y[test_index]
    
    #learn scaling from training data
    scaler.fit(x_train)
    #scale training and test data
    x_train = scaler.transform(x_train)
    x_test = scaler.transform(x_test)
#loop over every model on every fold and generate predictions based on the available training data
    for model in models:
        print(type(model).__name__)
        
        #knearestneighbors does not accept sample_weight
        if type(model).__name__!='KNeighborsRegressor':
            model.fit(x_train,y_train,sample_weight=sample_weight[train_index])
        else:
            model.fit(x_train,y_train)
        
        #generate test predictions
        y_preds = model.predict(x_test)
        #save predictions to the corresponding row
        data.loc[test_index, type(model).__name__] = y_preds
        
        #generate overall score for this model/fold (for observation)
        rmse = sqrt(mean_squared_error(y_test, y_preds))
#move rating column to the back
data['rating'] = data.pop('rating')
#create meta model
#remove rating and old features from dataset
meta_X,meta_y = data.iloc[:,-(len(models)+1):-1], data.iloc[:,-1]
#split data into train and test 
meta_x_train, meta_x_test, meta_y_train, meta_y_test = train_test_split(meta_X, meta_y, test_size=0.2)
#initialize and fit adaboost model
meta_model = AdaBoostRegressor()
meta_model.fit(meta_x_train, meta_y_train)
#generate predictions
meta_preds = meta_model.predict(meta_x_test)
#calculate overall ensemble rmse score
meta_score = sqrt(mean_squared_error(meta_y_test, meta_preds))
px.histogram(pd.DataFrame({'pred':meta_preds}),x='pred')

И наши новые прогнозируемые значения очень похожи на нашу обучающую выборку! При общем среднеквадратичном значении 0,181 по 5-звездочной рейтинговой системе мы в среднем соответствуем оценке врача в пределах одной звезды. Мы также можем увидеть, как каждая модель внесла свой вклад в ансамбль, с помощью следующей строки кода:

print(meta_model.feature_importances_)
[0.18907524 0.03818779 0.17733793 0.20602109 0.26546008 0.12391788]

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

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

  1. Из имени или фотографии врача мы можем извлечь такую ​​информацию, как возраст и раса, которые особенно полезны при необходимости связаться с пациентами с аналогичным происхождением.
  2. Медицинская школа, резидентура и все другое образование также были исключены - очевидно, что это важный фактор, используемый больницами при приеме на работу. Было бы абсолютно полезно объединить их образование со списком школ с международным рейтингом. То же самое, что и простое кодирование меток и определение того, как на практике соотносятся обзоры школы и пациентов, но для выполнения потребуется гораздо больший размер выборки.

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

Здесь определенно больше возможностей, чем просто для моей выгоды; В частности, в случае поиска врача помощь новым врачам и частным клиникам быстро получить пациентов помогает распределить спрос на уже работающих врачей, что в идеале снижает расходы на здравоохранение. И, в более широком смысле, наличие положительных или отрицательных рекомендаций для новых предприятий и продуктов позволяет потребителям быстро принимать обоснованные решения о вариантах, которые в противном случае не учитывались бы. Мы видим такого рода рекомендации в таких сервисах, как Netflix, где нам неясно, как они решили, что нам может понравиться конкретный фильм, но мы можем увидеть процент совпадения в любом случае. С другой стороны, Amazon не предлагает рекомендаций для непроверенных продуктов, что заставляет многих потребителей проводить значительный объем исследований или полностью игнорировать продукт (что затем приводит к тому, что компании отчаянно публикуют отзывы, иногда путем подкупа или скидки на их продукты).