TFM Aplicación de algoritmos escalables para el análisis y extracción de textos y búsqueda de patrones en el ámbito hospitalario

eBurnout Text analysis in the real dataset

@autor: Jesús García García

Carga de Datos

In [562]:
#Imports de Google Cloud Storage y librerías para el tratamiento de los datos
import google.datalab.storage as storage
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from io import BytesIO
%matplotlib inline

#Indicamos el nombre del Bucket de Google Cloud Storage en donde se encuentra el Dataset previamente procesado por DataPrep
mybucket = storage.Bucket('my-first-project-7d187')

#Indicamos el nombre del fichero CSV a tratar
data_csv = mybucket.object('eBurnout_DatasetReal31082018.csv')

#Lectura del fichero CSV
uri = data_csv.uri
%gcs read --object $uri --variable data

#Transformación de Dataset en columnas delimitadas por ;
df = pd.read_csv(BytesIO(data),delimiter=';',error_bad_lines=False,parse_dates=['Fecha toma'])

#Leemos las cinco primeras filas del Dataset (cabecera)
df.head()
Out[562]:
email usuario hospital edad especialidad estado civil sexo tipo contrato tipo trabajo tiempo vida laboral ... heart_max (1) heart_min(1) heart_max (2) heart_min(2) heart_max (3) heart_min(3) Fecha toma Temperatura max min Media historica temperatura burnout
0 [email protected] 1 Son Llatzer 30-34 Anestesiologia y Reanimacion soltero hombre adjunto hospitalario 1.0 ... 123.0 88.0 150.0 123.0 220.0 150.0 2018-07-17 32/19 31/18 NO
1 [email protected] 2 Son Llatzer 20-24 Analisis Clinicos casado hombre residente ambulatorio 2.0 ... 128.0 86.0 146.0 120.0 228.0 146.0 2018-06-08 25/13 26/14
2 [email protected] 3 Infanta Sofia 50-54 Angiologia y Cirugia Vascular casado mujer residente hospitalario 3.0 ... NaN NaN NaN NaN NaN NaN 2018-06-18 32/17 28/14 NO
3 [email protected] 4 Infanta Sofia 30-34 Anatomia Patologica soltero hombre residente urgencias 28.0 ... NaN NaN NaN NaN NaN NaN 2018-05-30 21/12 25/12 NO
4 [email protected] 5 Infanta Sofia 35-39 Psiquiatria casado mujer adjunto hospitalario 10.0 ... 128.0 92.0 156.0 128.0 220.0 156.0 2018-05-30 21/12 25/12

5 rows × 38 columns

Análisis y exploración del Dato

In [563]:
#Exploración de los tipos de datos del fichero
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 168 entries, 0 to 167
Data columns (total 38 columns):
email                          168 non-null object
usuario                        168 non-null int64
hospital                       166 non-null object
edad                           165 non-null object
especialidad                   165 non-null object
estado civil                   165 non-null object
sexo                           165 non-null object
tipo contrato                  165 non-null object
tipo trabajo                   165 non-null object
tiempo vida laboral            165 non-null float64
tiempo plaza actual            165 non-null float64
altura                         165 non-null float64
peso                           165 non-null float64
ejercicio                      165 non-null object
consent                        167 non-null float64
free text survey               167 non-null object
ae                             144 non-null float64
d                              144 non-null float64
rp                             144 non-null float64
duration_sleep                 50 non-null float64
efficiency_sleep               50 non-null float64
timeInBed                      50 non-null float64
deep                           49 non-null float64
light                          49 non-null float64
rem                            49 non-null float64
wake                           49 non-null float64
heart_max (0)                  85 non-null float64
heart_min(0)                   85 non-null float64
heart_max (1)                  85 non-null float64
heart_min(1)                   85 non-null float64
heart_max (2)                  85 non-null float64
heart_min(2)                   85 non-null float64
heart_max (3)                  85 non-null float64
heart_min(3)                   85 non-null float64
Fecha toma                     165 non-null datetime64[ns]
Temperatura max min            165 non-null object
Media historica temperatura    165 non-null object
burnout                        168 non-null object
dtypes: datetime64[ns](1), float64(23), int64(1), object(13)
memory usage: 50.0+ KB
In [564]:
# Comprobamos los datos faltantes
sns.heatmap(df.isnull(),yticklabels=False,cbar=False,cmap='viridis')
Out[564]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f4bd1d4b208>
In [565]:
# Sustituimos los datos faltantes o nulos por el valor media
df = df.fillna(df.mean())
sns.heatmap(df.isnull(),yticklabels=False,cbar=False,cmap='viridis')
Out[565]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f4bcbcb9048>
In [566]:
#Visualización de la longitud de las opiniones vertidas (y= frecuencia, x=total de opiniones) El 0 significa que hay muchos usuarios que no han opinado
df.dropna(subset=['free text survey'], inplace=True)  #Quitamos nulos para poder sacar la longitud del texto. Si no los quitamos el sistema detectará que los valores nulos float no tienen longitud
df['length'] = df['free text survey'].apply(len)
df['length'].plot(bins=50, kind='hist') 
Out[566]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f4bca04eb38>
In [567]:
#Descripción de la longitud de los mensajes (La longitud total aparece en el max)
df.length.describe()
Out[567]:
count    167.000000
mean      21.586826
std       20.201171
min        2.000000
25%        2.000000
50%       20.000000
75%       42.000000
max       62.000000
Name: length, dtype: float64
In [568]:
#Vamos a crear un nuevo df con las columnas de email y free text survey
dftext= df[['email','hospital','free text survey','length','burnout']].copy()
In [569]:
dftext.head()
Out[569]:
email hospital free text survey length burnout
0 [email protected] Son Llatzer "" 2 NO
1 [email protected] Son Llatzer Mejor sueldo 12
2 [email protected] Infanta Sofia "" 2 NO
3 [email protected] Infanta Sofia Cambiar de trabajo 18 NO
4 [email protected] Infanta Sofia Tendrian que contratar mas personal 35
In [570]:
#Borramos caracteres extraños del nuevo Dataset (Algunos de ellos no se eliminarán porque son útiles para la personalidad)
dftext['free text survey'] = df['free text survey'].str.replace(r"[/%_*\"\'@$,]", '')
#Normalizamos las longitudes correctas después de realizar la limpieza
dftext['length'] = dftext['length'].replace([2],[0])
In [571]:
#Comprobamos que la limpieza se ha realizado correctamente
dftext.head()
Out[571]:
email hospital free text survey length burnout
0 [email protected] Son Llatzer 0 NO
1 [email protected] Son Llatzer Mejor sueldo 12
2 [email protected] Infanta Sofia 0 NO
3 [email protected] Infanta Sofia Cambiar de trabajo 18 NO
4 [email protected] Infanta Sofia Tendrian que contratar mas personal 35
In [572]:
#Conteo de palabras


# Función para calcular la varianza
def var_row(row):
  l = []
  #Each entry separated by " " (espacio)
  for i in row.split(' '):
     l.append(len(i.split()))
  return np.var(l)


# Número de palabras por cada comentario
dftext['words_comment_token'] = dftext['free text survey'].astype(str).str.split()
dftext['words_comment'] = dftext['free text survey'].str.count(' ') + 1
dftext['words_comment'] = dftext['words_comment'].replace([1.0],[0.0])
# Varianza en el conteo de las palabras
dftext['variance_word_counts'] = dftext['free text survey'].apply(lambda x: var_row(x))


#Conteo de símbolos que pueden determinar la personalidad

#dftext['puntos_suspensivos'] = dftext['free text survey'].apply(lambda x: x.count('1'))

#Imprimimos info del Dataset
dftext.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 167 entries, 0 to 167
Data columns (total 8 columns):
email                   167 non-null object
hospital                166 non-null object
free text survey        167 non-null object
length                  167 non-null int64
burnout                 167 non-null object
words_comment_token     167 non-null object
words_comment           167 non-null float64
variance_word_counts    167 non-null float64
dtypes: float64(2), int64(1), object(5)
memory usage: 11.7+ KB
In [573]:
dftext.head()
Out[573]:
email hospital free text survey length burnout words_comment_token words_comment variance_word_counts
0 [email protected] Son Llatzer 0 NO [] 0.0 0.0
1 [email protected] Son Llatzer Mejor sueldo 12 [Mejor, sueldo] 2.0 0.0
2 [email protected] Infanta Sofia 0 NO [] 0.0 0.0
3 [email protected] Infanta Sofia Cambiar de trabajo 18 NO [Cambiar, de, trabajo] 3.0 0.0
4 [email protected] Infanta Sofia Tendrian que contratar mas personal 35 [Tendrian, que, contratar, mas, personal] 5.0 0.0
In [574]:
sns.jointplot(x='variance_word_counts',y='words_comment',data=dftext)
Out[574]:
<seaborn.axisgrid.JointGrid at 0x7f4bcf37b9e8>
In [575]:
#Ver usuarios que han participado más en el experimento (no se tiene en cuanta que hayan dejado un comentario o no)
groupedall = dftext.groupby('email').agg({'email':'count'})
groupedall.sort_values('email', ascending=False)
In [576]:
#Ver usuarios que han participado más en el experimento (que han dejado comentarios)
dfparticipaciontext = dftext.copy()
dfparticipaciontext.drop(dfparticipaciontext[dfparticipaciontext.words_comment == 0.0].index, inplace=True)
groupedcomment = dfparticipaciontext.groupby('email').agg({'email':'count'})
groupedcomment.sort_values('email', ascending=False)
In [577]:
#Imprimir SwarmPlot de la cantidad de palabras según email
sns.swarmplot(x="words_comment", y="email", data=dftext,size=13, linewidth=0)
Out[577]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f4bcfe31048>
In [578]:
#Creamos y dividimos los dos Dataframes en usuarios con y sin pulsera
dftextnopulsera= dftext.copy()
dftextpulsera= dftext.copy()
#Normalizamos los datos (Sin pulsera)
dftextnopulsera = dftextnopulsera[~dftextnopulsera['email'].isin(['[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]'])]
#Normalizamos los datos (Con pulsera)
dftextpulsera = dftextpulsera[dftextpulsera['email'].isin(['[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]','[email protected]'])]
In [579]:
dftextnopulsera.head()
Out[579]:
email hospital free text survey length burnout words_comment_token words_comment variance_word_counts
5 [email protected] Infanta Sofia 0 NO [] 0.0 0.0
8 [email protected] Infanta Sofia 0 NO [] 0.0 0.0
11 [email protected] Infanta Sofia Sentirme tenido mas en cuenta en la toma de de... 54 NO [Sentirme, tenido, mas, en, cuenta, en, la, to... 10.0 0.0
12 [email protected] Infanta Sofia 0 NO [] 0.0 0.0
13 [email protected] Infanta Sofia Reducir sobrecargas de trabajo (mas medicos ma… 48 [Reducir, sobrecargas, de, trabajo, (mas, medi... 7.0 0.0
In [580]:
dftextpulsera.head()
Out[580]:
email hospital free text survey length burnout words_comment_token words_comment variance_word_counts
0 [email protected] Son Llatzer 0 NO [] 0.0 0.0
1 [email protected] Son Llatzer Mejor sueldo 12 [Mejor, sueldo] 2.0 0.0
2 [email protected] Infanta Sofia 0 NO [] 0.0 0.0
3 [email protected] Infanta Sofia Cambiar de trabajo 18 NO [Cambiar, de, trabajo] 3.0 0.0
4 [email protected] Infanta Sofia Tendrian que contratar mas personal 35 [Tendrian, que, contratar, mas, personal] 5.0 0.0
In [581]:
#Imprimir SwarmPlot de la cantidad de palabras según email (NO PULSERA)
sns.swarmplot(x="words_comment", y="email", data=dftextnopulsera,size=13, linewidth=0)
Out[581]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f4bca350630>
In [582]:
#Imprimir SwarmPlot de la cantidad de palabras según email (SÍ PULSERA)
sns.swarmplot(x="words_comment", y="email", data=dftextpulsera,size=13, linewidth=0)
Out[582]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f4bcfdfffd0>
In [583]:
# Imprimir Plot cantidad de palabras vs email (NO PULSERA)
plt.figure(figsize=(15,10))
sns.violinplot(x='email', y='words_comment', data=dftextnopulsera, inner=None, color='lightgray')
sns.stripplot(x='email', y='words_comment', data=dftextnopulsera, size=4, jitter=True)
plt.ylabel('Words per comment')
plt.show()
In [584]:
# Imprimir Plot cantidad de palabras vs email (SÍ PULSERA)
plt.figure(figsize=(15,10))
sns.violinplot(x='email', y='words_comment', data=dftextpulsera, inner=None, color='lightgray')
sns.stripplot(x='email', y='words_comment', data=dftextpulsera, size=4, jitter=True)
plt.ylabel('Words per comment')
plt.show()
In [585]:
#Nube de palabras de todas las opiniones de todos los usuarios
from stop_words import get_stop_words
from wordcloud import WordCloud, STOPWORDS
import matplotlib.pyplot as plt
#stop_words = get_stop_words('es')
stopwords = get_stop_words('spanish') #https://pypi.org/project/stop-words/

def show_wordcloud(data, title = None):
    wordcloud = WordCloud(
        background_color='white',
        stopwords=stopwords,
        max_words=200,
        max_font_size=40, 
        scale=3,
        random_state=1 # chosen at random by flipping a coin; it was heads
    ).generate(str(data))

    fig = plt.figure(1, figsize=(12, 12))
    plt.axis('off')
    if title: 
        fig.suptitle(title, fontsize=20)
        fig.subplots_adjust(top=2.3)

    plt.imshow(wordcloud)
    plt.show()

show_wordcloud(dftext['free text survey'])
In [586]:
#Nube de palabras de todas las opiniones de los usuarios sin pulsera
from stop_words import get_stop_words
from wordcloud import WordCloud, STOPWORDS
import matplotlib.pyplot as plt
#stop_words = get_stop_words('es')
stopwords = get_stop_words('spanish') #https://pypi.org/project/stop-words/

def show_wordcloud(data, title = None):
    wordcloud = WordCloud(
        background_color='white',
        stopwords=stopwords,
        max_words=200,
        max_font_size=40, 
        scale=3,
        random_state=1 # chosen at random by flipping a coin; it was heads
    ).generate(str(data))

    fig = plt.figure(1, figsize=(12, 12))
    plt.axis('off')
    if title: 
        fig.suptitle(title, fontsize=20)
        fig.subplots_adjust(top=2.3)

    plt.imshow(wordcloud)
    plt.show()

show_wordcloud(dftextnopulsera['free text survey'])