Predice consulta a médico con aprendizaje automatico

Recientemente participé en un hackathon de aprendizaje automático en línea que utiliza el aprendizaje automático para predecir el precio de la consulta médica en India. Aunque no logré una gran predicción / resultado, sirvió como una sesión de aprendizaje para consolidar lo que había aprendido en los últimos meses. De todos modos, para aquellos que no lo sabían, provenía de una farmacia y adquiría habilidades de análisis de datos con mi propio plan de estudios. Si está interesado en involucrarse en este campo, asegúrese de revisar mi plan de estudios aquí .

Saltemos directamente al hackathon. Este es un conjunto de datos bastante pequeño con 5961 filas y 7 características únicas.

  1. Calificación del médico
  2. Experiencia del médico en años
  3. Perfil del médico
  4. Calificaciones otorgadas por los pacientes
  5. Miscellae_Info que contiene otra información sobre el médico
  6. Lugar (área y ciudad de la ubicación del médico)
  7. Tarifas cobradas por el médico (Variable dependiente)

En primer lugar, importe todas las dependencias y el conjunto de datos

 import numpy como np 
 import las pandas como pd 
 from sklearn.model_selection import gridSearchCV 
 from sklearn.preprocessing import StandardScaler [recurra en los campos de búsqueda de información] [sk456]. importar make_scorer
 df = pd.read_csv ("Final_Train.csv")

Ver los datos para tener una idea de la información proporcionada

 df.head ()

Claramente, es necesario limpiar algunos datos don e antes de cualquier proceso de modelado. Comencemos observando el número de valores faltantes en este conjunto de datos de entrenamiento.

 round (df.isnull (). Sum () / len (df) * 100,2)

La imagen de arriba muestra el porcentaje de faltantes Valores para cada columna. Voy a lidiar con estos valores faltantes individualmente, así que solo tenga esos valores en mente por ahora.

Siempre me gustaría comenzar desde las tareas más simples para ponerlo en marcha antes de abordar los problemas más complejos. La columna “Experiencia” aquí parece bastante simple ya que solo requiere extraer los valores enteros de la cadena.

 # Extraer años de experiencia 
 df ["Experience"] = df ["Experience"] .str.split () 
 df ["Experience"] = df ["Experience"] .str [0] .astype ("int")

La primera línea de código divide la cadena en una lista, mientras que la segunda extrae el primer elemento de la lista y la convierte en una integer.

A continuación, la columna “Lugar” se puede procesar fácilmente separando la Ciudad del área.

 # Extraer ciudades 
 df ["Place"] .fillna ("Unknown, Unknown", inplace = True) 
 df ["Place"] = df ["Place"] .str.split (",") 
 df ["City"] = df ["Place"] .str [-1]
 df ["Place"] = df ["Place"] .str [0]

Antes de la extracción, reemplacé todos los valores faltantes en esta columna con la cadena ‘Desconocido, Desconocido’ para representarlos. Nota adicional: a veces es una buena idea dar a los valores perdidos una clase separada en lugar de confiar en la técnica de imputación de valores perdidos como media / mediana / modo. Por ejemplo, en este conjunto de datos, es posible que algunas regiones de la India no hayan enumerado su ubicación durante la recopilación de datos, pero podrían haber venido de la misma región. A continuación, divida la cadena en ‘,’ y cree una nueva columna ‘Ciudad’ usando el último elemento de la lista.

Pasando a la columna “Calificaciones”, recuerde que esta columna tiene más del 50% de los valores perdidos. Tenemos que lidiar con los valores faltantes antes de cualquier otro procesamiento.

 # Clasificaciones separadas en contenedores 
 df ["Rating"] .fillna ("- 99%", inplace = True) 
 df ["Rating"] = df ["Rating"] .str [:-1] .astype ("int")
 bins = [-99,0,10,20,30,40,50,60,70,80,90,100]
 labels = [i for i in range(11)]
 df ["Rating"] = pd.cut (df ["Rating"]bins = bins, labels = labels, include_lowest = True)

Los valores faltantes se reemplazaron con -99% para diferenciarlos. Luego, asumiendo que una calificación de 91% no tiene una diferencia significativa como calificación de 99%, los agrupé en contenedores de tamaño 10. Los valores faltantes caerán en la clase 0 mientras que 0-9% serán de clase 1, 10–19% será la clase 2, y así sucesivamente. df [“Rating”] .value_counts (). sort_index () mostró la distribución.

Para las columnas “Calificación”, consta de varias calificaciones del médico sin ningún método de informe estandarizado. Empiezo haciendo la división normal y trato de tener una idea de la frecuencia de los diferentes términos que aparecen en esta columna.

 # Extraer la calificación relevante 
 df ["Qualification"] = df ["Qualification"] .str.split ( ",") 
 Calificación = {} 
for x in df ["Qualification"] .values: 
 for each one in x: 
 cada uno = each.strip () 
 if cada uno en la Clasificación: 
 Calificación [each] + = 1 
 else: 
 Calificación [each] = 1

Para ser honesto, estoy bastante perdido en esta etapa. Si observa el diccionario de Cualificación, una gran parte de la calificación solo tiene 1 aparición en todo el conjunto de datos y algunos de los términos en realidad se refieren a una calificación similar, pero se contabilizaron por separado. Por ejemplo, hubo entradas de “MBA -Healthcare” y “MBA” que creo que se refieren a la misma calificación. Este es el problema de la entrada de datos no estandarizada o la recopilación de datos y creo que los científicos / analistas de datos lo ven a diario. Decidí ir con el método más simple y simplemente identificar las 10 principales calificaciones que más ocurren.

 most_qua = ordenado (Qualification.items (), clave = lambda x: x [1]reverse = True) [:10]
 final_qua = []
 for tup en most_qua: 
 final_qua.append (tup [0])
 for el título en final_qua: 
 df MachineHack, Predict A Doctor’s Consultation Hackathon = 0 
    
 for x, y in zip (df ["Qualification"] .values, np.array ([idx for idx in range(len(df))])): 
 for q in x: 
 q = q.strip () 
 si q en final_qua: 
 df [q][y]  = 1
 df.drop ("Qualification", axis = 1, inplace = True)

El resultado final son variables dummies para la calificación de 10 frecuencias más altas en el conjunto de datos.

Ahora para la columna “Perfil”. Si puede recordar, no tenemos ningún valor faltante en esta columna. Una comprobación rápida de value_counts () produjo esto.

Eso es en realidad bastante bueno. Como la columna completa solo consta de 6 clases, oneHotEncoding la columna debería hacer el truco. Antes de eso, una comprobación rápida en la columna “Ciudad” que creamos mostró que también contiene un pequeño número de clases (10). Sin embargo, algo extraño apareció.

Hay una entrada “e” de la nada y supongo que debería haber un error (entrada incorrecta). Encontré que el problema ocurrió en la fila 3980 y cambié las columnas “Ciudad” y “Colocar” para esa fila a “desconocido”.

 df ["City"][3980]  = "Desconocido" 
 df ["Place"][3980]  = " Desconocido "

Una revisión final.

Mucho mejor. Luego, al usar solo 1 línea de código, podremos generar variables ficticias para las columnas ‘Perfil’ y ‘Ciudad’ al mismo tiempo.

 # Get dummies 
 df = pd.get_dummies (df, columns = ["City","Profile"]prefijo = ["City","Profile"])

Por último, aquí hay una breve vista previa de la columna ‘Información_Múltiple’

Teniendo en cuenta el alto porcentaje de valores faltantes (43,95%), y el hecho de que no pude encontrar ninguno relevancia de la columna (no soy un experto en PNL), decidí renunciar a la columna y simplemente soltarla. Por supuesto, no es la mejor manera, pero lo haré por ahora.

 df.drop ("Información_Múltiple", eje = 1, lugar = verdadero)

Una vez que hayamos terminado con el preprocesamiento de datos, el siguiente paso debería ser naturalmente Visualización de datos. Nota * algunas personas prefirieron no mirar sus datos antes de modelar para evitar cualquier sesgo introducido por el analista, pero a cada uno lo suyo.

La mayoría de las personas habría asumido que existe alguna asociación entre la experiencia del médico y las tarifas que cobran . De hecho existe, pero podría no ser lo que esperamos que sea. Las tarifas promedio aumentaron con la experiencia, pero alcanzaron un pico en aproximadamente 25 años de experiencia, luego, las tarifas promedio disminuyeron con un número cada vez mayor de experiencia.

Las calificaciones son otra variable interesante a considerar. Si recuerdas, agrupamos la clasificación en contenedores de tamaño 10, incluido el valor más pequeño. Por ejemplo, bin 5 tendrá una calificación de 40–49%, bin 10 será 90–100% y bin 0 solo serán los valores faltantes en el conjunto de datos. Como puede ver, una calificación alta no se correlaciona con una tarifa más alta cobrada (de hecho, ¡una tarifa más baja podría ser la razón de una calificación alta!), Y las tarifas promedio más altas que se cobraron fueron en realidad del 30 al 60%. El esquema de color representa el nivel de experiencia mediana en cada recipiente, con el verde oscuro que representa una experiencia mediana más alta. La experiencia mediana en los contenedores 4 y 5 fue de 27 años y 31 años respectivamente, mientras que el contenedor 10 solo tiene una experiencia promedio de 14 años, lo que justifica la capacidad de los médicos para cobrar una tarifa más alta en esos contenedores.

Hay mucho por desvelar aquí.

  1. Las tarifas cobradas por los médicos difieren entre ciudades
  2. ¡La distribución de los diferentes perfiles de médicos dentro de cada ciudad es similar para la mayoría de las ciudades
  3. Todas las entradas con ciudades desconocidas son en realidad dermatólogas!

los datos faltantes pueden no ser aleatorios y pueden deberse al proceso de recopilación de datos, este es un muy buen ejemplo de ello. De alguna manera, los dermatólogos en alguna ciudad no están grabando su ubicación.

Nota * Todas las visualizaciones se realizan en Tableau solo porque estoy aprendiendo la herramienta. Puedes usar Python o cualquier otra herramienta de visualización.

Finalmente, podemos modelar nuestros datos y hacer un aprendizaje automático genial. Decidí usar una máquina de vectores de soporte para esta tarea, ya que se puede usar tanto para problemas lineales como no lineales. La pequeña cantidad de datos tampoco garantiza el uso de una red neuronal.

Antes de implementar los algoritmos, tenemos que codificar las variables categóricas y escalar sus características.

 X = df.drop ("Fees", axis = 1) 
 y = df ["Fees"]
 # Codificación 
 enc = OrdinalEncoder () 
 X = enc.fit_transform (X)
 X_train, X_test, y_test = train_test_sp_pest_sp_pest_sp_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_pest_service_test_pest_pest_pest , y, test_size = 0.2)
 # feature scaling 
 scaler = StandardScaler () 
 X_train = scaler.fit_transform (X_train)

Implementing SVR…

 # support vector machine 
 from sklearn .svm import SVR 
 m = SVR (gamma = "scale") 
 m.fit (scaler.transform (X_train), y_train)

En función del sitio de hackathon, la presentación se evalúa en Root-Mean Error de registro cuadrado (RMSLE), más específicamente, 1-RMSLE.

 puntuación de definición (y_pred, y): 
 y_pred = np.log (y_pred) 
 y = np.log (y) 
 devuelve 1 - ((np.sum ((y_pred-y) ** 2)) / len (y)) * * 1/2
 # Prediction 
 y_pred = m.predict (scaler.transform (X_test)) 
 puntuación (y_pred, y_test)

Nuestra predicción en el conjunto de pruebas nos dio una puntuación de 0.7733490738717279. Si echas un vistazo a la tabla de clasificación, el ganador con la mejor puntuación es 0.76162342. Por supuesto, nuestro conjunto de pruebas no es el verdadero conjunto de pruebas utilizado para la tabla de clasificación y no es comparable. Pero nos dio una métrica para utilizarla en una optimización adicional.

Hacer un GridSearchCV es una excelente manera de hacer ajustes de hiperparámetros, pero tome nota de la potencia de cálculo necesaria especialmente para algoritmos como SVM que no se escalan bien.

 # Defina su propio anotador 
 anotador = make_scorer (score, greater_is_better = True)
 # tuneo hiperparamétrico 
 parámetros = {"C": [0.1,1,10]"kernel": ["linear","rbf","poly"]} 
 reg = GridCVCVCV (m, param_grid = parámetros, scoring = scorer, n_jobs = -1, cv = 5)
 reg.fit (X_train, y_train)

La ejecución de reg.best_params_ proporcionó la combinación de hiperparámetros que proporcionan la mejor puntuación. Los mejores hiperparámetros en este caso son C = 10, y kernel = “rbf”. Nota * defina su propio anotador para usarlo en GridSearchCV para que se optimice usando esa matriz de puntuación.

Finalmente.

 y_pred_tuned = reg.predict (scaler.transform (Xtest_test), 
 score (y_pred_tuned, y_test_tuned, y_test) 19659014] El puntaje obtenido aquí es 0.8034644306855361. Una ligera mejora pero una mejora, no obstante. Esto se traduce en una puntuación de 0.72993077 utilizando el conjunto de pruebas para la tabla de clasificación y el rango 62/169 (percentil 40 superior)

Ahí lo tienen. Un recorrido completo de una competencia de aprendizaje automático. Hay muchos más que podría haber hecho para lograr una mejor puntuación, hacer más ingeniería de características, usar otros algoritmos, pero desafortunadamente, no pude hacerlo antes de que terminara la competencia. Hacer tal competencia es útil para consolidar lo que has aprendido y una excelente manera de practicar. A menos que esté apuntando a los premios, ganar no importa a menudo y es el viaje el que más satisface.

Todo el código se puede encontrar en mi GitHub aquí .


MachineHack, Predict A Doctor’s Consultation Hackathon se publicó originalmente en Towards Data Science en Medium, donde las personas continúan la conversación destacando y respondiendo a esta historia.



		

Dejá un comentario