-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmemoria.Rmd
More file actions
1666 lines (1327 loc) · 146 KB
/
memoria.Rmd
File metadata and controls
1666 lines (1327 loc) · 146 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
output:
pdf_document:
includes:
before_body: plantillas/portadaLatex.tex
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, tidy.opts=list(width.cutoff=60), tidy=TRUE)
```
\newpage
<!---------------- ÍNDICE DE CONTENIDOS ---------------->
\thispagestyle{empty}
\tableofcontents
\newpage
<!------------------------------------------------------>
# Descripción del dataset
El dataset escogido para realizar las prácticas de esta asignatura es el ***UCI ML Drug Review dataset*** [1]. En él se puede encontrar una gran cantidad de información acerca de la efectividad de los fármacos recetados para las enfermedades diagnosticadas así como la opinión de los pacientes al respecto. Para ello se recopilan hasta siete campos, cuya naturaleza puede ser numérica o textual. Los campos más relevantes son *drugName*, en el que se encuentran los medicamentos recetados, *condition* que recopila las enfermedades, *review* en el que se almacenan las críticas de los pacientes con respecto a sus respectivas dolencias y tratamientos, así como *rating* que es un valor numérico entre 0 y 10 que el paciente le asigna a un tratamiento según su efectividad. Asimismo, también dispone de un campo numérico denominado *usefulCount* que representa el número de pacientes a los que les ha parecido útil la opinión de otro paciente.
Otros campos menos relevantes son *uniqueID* con el que se representa el identificador único para cada una de las críticas de los pacientes así como *date*, que hace referencia a la fecha de publicación de las mismas.
Una cualidad que debemos destacar es que en la propia página de *Kaggle* de donde obtuvimos este dataset [1] ya están divididos los datos en dos conjuntos independientes: uno para entrenamiento y otro para test. La elección de este dataset se fundamenta en diversos motivos. El más importante es que cumple con los requisitos establecidos por el profesor para, posteriormente, aplicar los algoritmos vistos en esta asignatura. Sin embargo, también tuvimos en cuenta la organización de competiciones de Machine Learning en las que se proponía este dataset como principal protagonista y, además, aportan ciertas ideas para aplicar los tipos de algoritmos más populares, como veremos a continuación.
# Objetivos y estructura del documento
Tal y como hemos podido comprobar en la descripción anterior del dataset, el campo más relevante es el de las *reviews* de los pacientes. Es por ello por lo que la mayoría de las técnicas que aplicaremos a lo largo de este documento se enfocarán hacia el uso de este atributo. A continuación mostramos la estructura que va a seguir este documento:
- En primer lugar analizaremos estadísticamente el conjunto de entrenamiento original proporcionado por *Kaggle* para conocer cómo están dispuestos los datos, sus clases mayoritarias y minoritarias, así como los datos característicos más relevantes.
- A continuación reduciremos el tamaño de sendos conjuntos originales hasta que estén compuestos por 5.000 registros cada uno.
- Continuaremos realizando un preprocesamiento de las opiniones de los pacientes con el objetivo de quedarnos sólamente con los términos más relevantes.
- Realizaremos dos análisis generales para comprobar si disponemos de variables correlacionadas así como la influencia de cada una de ellas para trabajar solo con aquellos campos que nos aporten información útil.
Una vez dispongamos de los dos conjuntos de datos normalizados y de las *reviews* de los pacientes preprocesadas, aplicaremos las siguientes técnicas:
- **Nube de palabras**. Obtendremos los términos más frecuentemente utilizados en las críticas de los pacientes para conocer los temas que tratan en sus respectivas opiniones.
- **Análisis de sentimientos**. Con esta técnica pretendemos conocer si predominan las opiniones positivas, negativas así como cuántas de ellas se podrían considerar como neutras.
- **Reglas de asociación**. En esta sección intentaremos conocer las relaciones existentes entre los diferentes términos obtenidos para conocer en un mayor grado de profunidad de los temas que hablan los pacientes en sus críticas.
- **Clasificación**. En esta sección se aplicarán diversos algoritmos vistos en clase con el objetivo de conocer cuál es la técnica que mejor tasa de error proporciona partiendo del objetivo propuesto en la propia página del dataset [1]: **predecir la enfermedad del paciente en función de su review**. De este modo, obtendremos un clasificador capaz de predecir la dolencia que padece un paciente en función de la opinión que escriba.
- **Regresión**. En este apartado llevaremos a cabo una modificación de la idea que se propone para regresión en la página del dataset [1], es decir, intentaremos conocer cuál es la variable que mejor predice la **efectividad del medicamento**. Para ello haremos uso de las dos principalmente relacionadas con este campo: **la puntuación del fármaco y las reviews de los pacientes**. Para ello aplicaremos diversos tipos de regresión con tal de comparar la capacidad de predicción de cada clasificador en función de la variable predictora.
- **Agrupamiento**. Aplicaremos diversos algoritmos de agrupamiento para encontrar que algoritmos se adecuan mejor a nuestros datos de forma que podamos agrupar los datos de la mejor forma posible, extrayendo así información valiosa del dataset. Ademas de esto, en esa misma sección se utilizarán tecnicas de **text mining** junto con las de agrupamiento de forma que podamos estudiar las valoraciones sobre un medicamento y obtener así conclusiones sobre la estructura del dataset.
- **Bibliografía**. En este último apartado se adjuntan los enlaces utilizados para llevar a cabo la práctica.
# Análisis exploratorio de los datos del conjunto de entrenamiento original.
Tal y como hemos comentado anteriormente, este dataset que hemos seleccionado proporciona dos conjuntos, uno de entrenamiento y otro de prueba, en los que se recogen las opiniones de los pacientes, diagnosticados de una determinada enfermedad, acerca de los tratamientos que han probado. Existen dos tipos de opiniones, principalmente, una de ellas consiste en valorar el medicamento recetado para una enfermedad en concreto de 0 a 10. Por otro lado también se recopila, para cada opinión de un paciente, el número de personas que consideran que su crítica es útil.
Para comenzar con el estudio estadístico cargamos los datos de entrenamiento.
```{r, warning=FALSE, tidy=TRUE}
# Establecemos una semilla para que los resultados sean reproducibles.
set.seed(0)
# Cargamos la biblioteca que utilizaremos para graficar los datos estadísticos.
library(ggplot2)
# Cargamos los datos de entrenamiento y test
training_dataset <- read.csv(file="./drugsComTrain_raw.csv", header=TRUE, sep=",")
test_dataset <- read.csv(file="./drugsComTest_raw.csv", header=TRUE, sep=",")
```
A continuación presentamos las estadísticas que hemos pensado son más relevantes para nuestro dataset en particular. En primer lugar averiguamos las diez enfermedades más comunes que se encuentran registradas en nuestro conjunto de datos con el objetivo de visualizar aquellas dolencias que se presentan más frecuente en un centro sanitario.
```{r, tidy=TRUE}
# 1) Enfermedades más comunes.
# Extraemos un resumen acerca de las enfermedades registradas.
recuento_enfermedades<-summary(training_dataset$condition)
# Convertimos los resultados anteriores en una matriz para poder trabajar con ellos.
enfermedades_matriz<-data.matrix(recuento_enfermedades)
# Obtenemos los nombres de las dolencias.
enfermedades<-names(recuento_enfermedades)
# También obtenemos el número de muestras de cada enfermedad.
recuento<-enfermedades_matriz[1:length(enfermedades_matriz)]
# Componemos un data frame con las enfermedades y el número de muestras para cada una.
dataframe<-data.frame(enfermedades=enfermedades[1:10], recuento=recuento[1:10])
# Las representamos en un gráfico de barras.
ggplot(data=dataframe, aes(x=enfermedades, y=recuento)) + geom_bar(stat="identity")+coord_flip()+ggtitle('Enfermedades más comunes.')
```
Tal y como se puede comprobar que la enfermedad *Birth Control* es consideráblemente mayoritaria frente al número de muestras asociadas a las otras nueve enfermedades. Por ello, cuando reduzcamos nuestro conjunto de entrenamiento y test, deberemos tener en cuenta este aspecto de modo que todas las enfermedades escogidas tengan un número de muestras suficiente para ser representadas de forma equilibrada.
A continuación realizaremos el mismo experimento en relación a los medicamentos con el fin de conocer cuáles son los más recetados de forma general.
```{r, tidy=TRUE}
# 2) Medicamentos más comunes.
# Repetimos el procedimiento anterior para los medicamentos.
recuento_medicamentos<-summary(training_dataset$drugName)
medicamentos_matriz<-data.matrix(recuento_medicamentos)
medicamentos<-names(recuento_medicamentos)
recuento<-medicamentos_matriz[1:length(medicamentos_matriz)]
dataframe<-data.frame(medicamentos=medicamentos[1:10], recuento=recuento[1:10])
ggplot(data=dataframe, aes(x=medicamentos, y=recuento)) + geom_bar(stat="identity")+coord_flip()+ggtitle('Medicamentos más comunes.')
```
Una vez hemos exploradas las enfermedades y medicamentos más comunes, los dos siguientes datos estadísticos consisten en asociar estos dos factores con el objetivo de conocer qué medicamentos más comunes se encuentran asociados a una misma enfermedad y cuáles de las enfermedades más comunes se encuentran asociadas para un mismo medicamento. En esta tercera estadística nos ocuparemos del primer caso.
```{r message=FALSE, warning=FALSE, tidy=TRUE}
# 3) Enfermedades comunes asociadas con un medicamento.
# Partimos del mismo calculo que en el apartado anterior donde obtenemos los medicamentos mas comunes
recuento_medicamentos<-summary(training_dataset$drugName)
matriz_medicamentos<-data.matrix(recuento_medicamentos)
medicamentos<-names(recuento_medicamentos)
# Creo un dataframe vacio donde meter los medicamentos mas comunes con sus enfermedades
dataframe_total<-data.frame()
# Bucle que itera sobre los 5 medicamentos más comunes
n_medicamento<-1
for(medicamento in medicamentos[1:5]){
# Extraigo las filas del medicamento en cuestión
df_medicamento<-training_dataset[training_dataset$drugName == medicamento, ]
# Apunto el número de veces que se repite cada enfermedad
recuento_enfermedades<-summary(df_medicamento$condition)
# Transformo el dataframe a matriz
enfermedades_matriz<-data.matrix(recuento_enfermedades)
# Apunto el nombre de las enfermedades asociado al recuento de estas
enfermedades<-names(recuento_enfermedades)
# También obtenemos el número de muestras de cada enfermedad
recuento<-enfermedades_matriz[1:length(enfermedades_matriz)]
# Creo un dataframe computesto por las 5 enfermedades más comunes con su número
dataframe<-data.frame(enfermedades=enfermedades[1:5], recuento=recuento[1:5])
# Creo el título del gráfico
text <-(paste0('Enfermedades más comunes para ', medicamento))
# Creo el gráfico para ese medicamento
ggplot(data=dataframe, aes(x=enfermedades, y=recuento)) + geom_bar(stat="identity")+coord_flip()+ggtitle(text)
titulo<-paste("estadisticas_medicamento", n_medicamento, sep="")
titulo<-paste(titulo,".png",sep="")
ggsave(titulo)
n_medicamento<-n_medicamento+1
}
```
\begin{figure}[h]
\centering
\includegraphics[width=0.75\textwidth]{Imagenes/estadisticas_medicamento1.png}
\caption{Estadísticas para el medicamento Levonorgestrel.}
\label{estadisticas_medicamento1}
\end{figure}
Como podemos comprobar, los cinco medicamentos más comunes son especialmente recetados para paliar la enfermedad denominada *Birth Control*. Del mismo modo podemos observar que el resto de dolencias a estos cinco tratamientos también están especializados en casos relacionados con la contracepción así como enfermedades asociadas con la menstruación y los órganos genitales femeninos. Esto nos indica que diversos tipos de dolencias, principalmente femeninas, pueden tratarse con los mismos medicamentos puesto que son indicados para un conjunto específico de dolencias de esta naturaleza en particular. Asimismo, podemos afirmar que si bien hemos comprobado que la enfermedad representada con un mayor número de muestras es *Birth Control*, tiene sentido que los cinco medicamentos más comunes estén dedicados a paliar esta dolencia, que también es sumamente común. Por tanto, podemos afirmar la relación existente entre los medicamentos más ampliamente recetados con las dolencias más altamente registradas.
Para el siguiente caso realizaremos el mismo cálculo pero en orden inverso, es decir, comprobaremos cuáles son los medicamentos más comunes para una misma enfermedad. De este modo podremos observar los distintos tratamientos que se pueden aplicar a una misma enfermedad además de comprobar su popularidad a la hora de recetar cada uno de ellos.
```{r message=FALSE, warning=FALSE, tidy=TRUE}
# 4) Medicamentos para cada enfermedad más común.
# Realizamos el mismo calculo que en el apartado anterior pero adaptandolo al caso contrario
recuento_enfermedades<-summary(training_dataset$condition)
matriz_enfermedades<-data.matrix(recuento_enfermedades)
enfermedades<-names(recuento_enfermedades)
dataframe_total<-data.frame()
n_enfermedad<-1
for(enfermedad in enfermedades[1:5]){
df_enfermedades<-training_dataset[training_dataset$condition == enfermedad, ]
recuento_medicamentos<-summary(df_enfermedades$drugName)
medicamentos_matriz<-data.matrix(recuento_medicamentos)
medicamentos<-names(recuento_medicamentos)
recuento<-medicamentos_matriz[1:length(medicamentos_matriz)]
dataframe<-data.frame(medicamentos=medicamentos[1:5], recuento=recuento[1:5])
text <-(paste0('Medicamentos más comunes para ', enfermedad))
ggplot(data=dataframe, aes(x=medicamentos, y=recuento)) + geom_bar(stat="identity")+coord_flip()+ggtitle(text)
titulo<-paste("estadisticas_enfermedad", n_enfermedad, sep="")
titulo<-paste(titulo,".png",sep="")
ggsave(titulo)
n_enfermedad<-n_enfermedad+1
}
```
\begin{figure}[h]
\centering
\includegraphics[width=0.75\textwidth]{Imagenes/estadisticas_enfermedad1.png}
\caption{Estadísticas para la primera enfermedad.}
\label{estadisticas_enf1}
\end{figure}
En este caso, como se puede ver en las diferentes gráficas, los medicamentos se recetan de forma más distribuida, es decir para una misma enfermedad hay una mayor gama de tratamientos disponibles que se recetan con una frecuencia similar. Por lo tanto, no existen tratamientos recetados cláramente mayoritarios, como sucedía en el caso anterior.
En el siguiente dato estadístico estudiaremos la valoración de los medicamentos para cada una de las diez enfermedades más comunes con el objetivo de conocer la eficacia, según los pacientes, de los tratamientos que en general se recetan para cada dolencia.
```{r, tidy=TRUE}
# 5) Eficacia general de los tratamientos recetados para las diez enfermedades más comunes.
# 5.1) Puntuación de los medicamentos asociados a "Birth Control".
# Extraemos un dataset solo con los registros asociados a la enfermedad en cuestión.
birth_control_dataset = subset(training_dataset, training_dataset$condition=="Birth Control")
# Calculamos variables estadísticas como la mediana, la media, el mínimo y máximo así como los cuartiles.
print("Estadísticas sobre los medicamentos para 'Birth Control'.")
summary(birth_control_dataset$rating)
# 5.2) Puntuación de los medicamentos asociados a "Depression".
depression_dataset = subset(training_dataset, training_dataset$condition=="Depression")
print("Estadísticas sobre los medicamentos para 'Depression'.")
summary(depression_dataset$rating)
# 5.3) Puntuación de los medicamentos asociados a "Pain".
pain_dataset = subset(training_dataset, training_dataset$condition=="Pain")
print("Estadísticas sobre los medicamentos para 'Pain'.")
summary(pain_dataset$rating)
# 5.4) Puntuación de los medicamentos asociados a "Anxiety".
anxiety_dataset = subset(training_dataset, training_dataset$condition=="Anxiety")
print("Estadísticas sobre los medicamentos para 'Anxiety'.")
summary(anxiety_dataset$rating)
# 5.5) Puntuación de los medicamentos asociados a "Acne".
acne_dataset = subset(training_dataset, training_dataset$condition=="Acne")
print("Estadísticas sobre los medicamentos para 'Acne'.")
summary(acne_dataset$ratingng)
# 5.6) Puntuación de los medicamentos asociados a "Bipolar Disorde".
bipolar_disorde_dataset = subset(training_dataset, training_dataset$condition=="Bipolar Disorde")
print("Estadísticas sobre los medicamentos para 'Bipolar Disorde'.")
summary(bipolar_disorde_dataset$rating)
# 5.7) Puntuación de los medicamentos asociados a "Insomnia".
insomnia_dataset = subset(training_dataset, training_dataset$condition=="Insomnia")
print("Estadísticas sobre los medicamentos para 'Insomnia'.")
summary(insomnia_dataset$rating)
# 5.8) Puntuación de los medicamentos asociados a "Weight Loss".
weight_loss_dataset = subset(training_dataset, training_dataset$condition=="Weight Loss")
print("Estadísticas sobre los medicamentos para 'Weight Loss'.")
summary(weight_loss_dataset$rating)
# 5.9) Puntuación de los medicamentos asociados a "Obesity".
obesity_dataset = subset(training_dataset, training_dataset$condition=="Obesity")
print("Estadísticas sobre los medicamentos para 'Obesity'.")
summary(obesity_dataset$rating)
# 5.10) Puntuación de los medicamentos asociados a "ADHD".
adhd_dataset = subset(training_dataset, training_dataset$condition=="ADHD")
print("Estadísticas sobre los medicamentos para 'ADHD'.")
summary(adhd_dataset$rating)
```
Como podemos comprobar en los resultados anteriores, si consideramos las medianas calculadas, es decir, la puntuación que se encuentra en la mitad del ranking, las enfermedades cuyos tratamientos, en general, son mejores valorados por los pacientes son *Pain, Anxiety, Acne, Weight Loss* y *Obesity*. Por el contrario, la que peor mediana tiene es *Birth Control*. Una de las ventajas de utilizar la mediana reside en la resistencia asociada a los valores de las puntuaciones, puesto que para calcularla lo único que se necesita es ordenar dichos valores y escoger el que se encuentra en la posición intermedia. Sin embargo, la media sí que se encuentra influida por los valores de las puntuaciones y, por lo tanto, se ve sesgada, principalmente, por los valores más bajos. Sin embargo, a través de esta variable estadística podemos confirmar las mismas conclusiones extraídas anteriormente, y además, nos permite agregar un mayor grado de concreción con respecto a los tratamientos mejor valorados. En este caso, podemos comprobar que la enfermedad cuyos tratamientos son mejor valorados es **Weight Loss, seguida de Obesity y Anxiety**.
A continuación procedemos cuáles son los medicamentos más efectivos, según la población, para estas tres enfermedades, con el objetivo de conocer las puntuaciones de los tratamientos que han provocado los buenos resultados observados anteriormente. Sin embargo, como el número de registros para cada una de las enfermedades es sumamente considerable, vamos a agregar una segunda condición. Esta trata de conseguir los medicamentos que a su vez son los más recetados. De este modo podremos conocer cuáles son los tratamientos más efectivos, según los pacients afectados por las tres enfermedades anteriores, además de los más comúnmente recetados. Con todos estos criterios los resultados, para esta dolencia en concreto, se pueden visualizar a continuación.
```{r, tidy=TRUE}
# 6) Medicamentos con mejores críticas y más veces recetados.
# 6.1) Para la primera enfermedad: 'Weight Loss'.
# Extraemos un dataset con las muestras asociadas solo a la enfermedad Weight Loss y con solamente los
# medicamentos que tengan una valoración 10/10.
birth_control_dataset = subset(training_dataset, training_dataset$condition=="Weight Loss" & training_dataset$rating==10)
# Extraemos un resumen estadístico acerca de este pequeño dataset de Weight Loss.
recuento_medicamentos_weight_loss = summary(birth_control_dataset$drugName)
# Convertimos los datos a una matriz para poder trabajar con ellos.
medicamentos_matriz<-data.matrix(recuento_medicamentos_weight_loss)
# Obtenemos los nombres de los medicamentos para esta enfermedad en particular.
medicamentos<-names(recuento_medicamentos_weight_loss)
# Obtenemos el número de veces que han sido recetados cada uno de ellos.
recuento<-medicamentos_matriz[1:length(medicamentos_matriz)]
# Componemos un dataframe con estos últimos datos.
dataframe<-data.frame(medicamentos=medicamentos[1:10], recuento=recuento[1:10])
# Lo representamos mediante un gráfico de barras.
ggplot(data=dataframe,aes(x=medicamentos, y=recuento)) + geom_bar(stat="identity")+coord_flip()+ggtitle('Mejores medicamentos para "Weight Loss"')
# 6.2) Para la segunda enfermedad: 'Obesity'
obesity_dataset = subset(training_dataset, training_dataset$condition=="Obesity" & training_dataset$rating==10)
recuento_medicamentos_obesity = summary(obesity_dataset$drugName)
medicamentos_matriz<-data.matrix(recuento_medicamentos_obesity)
medicamentos<-names(recuento_medicamentos_obesity)
recuento<-medicamentos_matriz[1:length(medicamentos_matriz)]
dataframe<-data.frame(medicamentos=medicamentos[1:10], recuento=recuento[1:10])
ggplot(data=dataframe,aes(x=medicamentos, y=recuento)) + geom_bar(stat="identity")+coord_flip()+ggtitle('Mejores medicamentos para "Obesiy"')
# 6.3) Para la tercera enfermedad: 'Anxiety'
anxiety_dataset = subset(training_dataset, training_dataset$condition=="Anxiety" & training_dataset$rating==10)
recuento_medicamentos_anxiety = summary(anxiety_dataset$drugName)
medicamentos_matriz<-data.matrix(recuento_medicamentos_anxiety)
medicamentos<-names(recuento_medicamentos_anxiety)
recuento<-medicamentos_matriz[1:length(medicamentos_matriz)]
dataframe<-data.frame(medicamentos=medicamentos[1:10], recuento=recuento[1:10])
ggplot(data=dataframe,aes(x=medicamentos, y=recuento)) + geom_bar(stat="identity")+coord_flip()+ggtitle('Mejores medicamentos para "Anxiety"')
```
En el primer gráfico resultante podemos comprobar que existe un medicamento con una puntuación máxima y que, a su vez, es cláramente el más recetado denominado 'Phentermine/topiramate'. Por ello podemos determinar que en función de las críticas de los pacientes afectados por esta dolencia en concreto, este es el tratamiento mejor valorado y más comúnmente recetado.
No sucede lo mismo con el segundo gráfico en el que se muestran los diez medicamentos con una puntuación máxima y, a su vez, lo más recetados. En este caso existen dos tratamientos con un número bastante similar de recetas, por lo que podemos pensar que son los dos medicamentos más eficaces contra esta dolencia además de ser los dos más recetados.
Por último, en el tercer gráfico podemos observar que se plantea la misma dinámica que en el primero, es decir, de nuevo existe un medicamento contra, en este caso, la ansiedad que tiene la mejor puntuación posible (10 de 10) y que es el más recetado.
# Preprocesamiento de los datos.
Realizando el análisis exploratorio anterior, nos percatamos de que existen **reviews repetidas para una misma enfermedad**. Creemos que es debido a que para una misma dolencia se han recetado varios medicamentos y cuando el paciente ha aportado su opinión, esta se ha asignado a cada uno de los tratamientos recetados para la misma enfermedad los cuales ocupan un registro distinto. Por ello, actualmente disponemos de reviews iguales para varias filas dentro del conjunto de datos de entrenamiento y de prueba, asociadas a una misma enfermedad y a los diversos tratamientos recetados para paliarla. Con el objetivo de poder aplicar, posteriormente, los algoritmos vistos en esta asignatura y así obtener resultados representativos, vamos a aplicar un procesamiento equivalente a eliminar aquellos registros que dispongan de la misma crítica tanto en el dataset de entrenamiento como en el de prueba.
```{r, tidy=TRUE}
## Conjunto de entrenamiento
# Eliminamos las filas que tengan la misma crítica.
new_training_dataset<-training_dataset[!duplicated(training_dataset$review), ]
# Comprobamos que no existen reviews repetidas.
reviews_repetidas<-new_training_dataset[duplicated(new_training_dataset$review)]
ncol(reviews_repetidas)
## Conjunto de prueba.
# Realizamos las mismas operaciones anteriores.
new_test_dataset<-test_dataset[!duplicated(test_dataset$review), ]
reviews_repetidas<-new_test_dataset[duplicated(new_test_dataset$review)]
ncol(reviews_repetidas)
```
Una vez hemos eliminado aquellos registros que disponen de la misma crítica, comenzaremos con el submuestreo del dataset para **reducir su tamaño**. Y es que, tal y como se ha podido comprobar en los resultados estadísticos anteriores, el número de muestras del conjunto de entrenamiento es bastante considerable. Asimismo, también hemos podido comprobar que existen clases de medicamentos con un número de muestras extremadamente mayoritario, como es *Birth Control*, por lo que no todas cuentan con la misma representación dentro del dataset. Es por ello por lo que, a continuación, se procede a aplicar un tratamiento consistente en reducir el conjunto de entrenamiento a 5.000 muestras. Para ello reduciremos el **número de enfermedades a 10 y para cada una de ellas obtendremos 500 muestras**. De este modo equilibramos las diversas clases de medicamentos existentes para, posteriormente, poder aplicar diversos algoritmos.
```{r, tidy=TRUE}
## CONJUNTO DE ENTRENAMIENTO
# Obtenemos un resumen acerca de las enfermedades registradas en el dataset.
recuento_etiquetas<-summary(new_training_dataset$condition)
# Recopilamos los nombres de las enfermedades.
etiquetas<-names(recuento_etiquetas)
# Formamos un dataset más pequeño con las 10 enfermedades con mayor número de ejemplos y les asociamos 500 muestras para cada una.
n_enfermedades_max = 10
n_muestras_max = 500
# Primera enfermedad
mini_training_dataset<-head(subset(new_training_dataset, new_training_dataset$condition==etiquetas[1]), n_muestras_max)
# Resto de enfermedades
for (i in c(2:n_enfermedades_max)) {
mini_training_dataset<-rbind(mini_training_dataset, head(subset(new_training_dataset,
new_training_dataset$condition==etiquetas[i]), n_muestras_max))
}
# Desorganizamos los datos
set.seed(42)
rows<-sample(nrow(mini_training_dataset))
mini_training_dataset<-mini_training_dataset[rows,]
# Repetimos el proceso que se ha aplicado anteriormente para la obtención de ciertas estadísticas para comprobar que realmente el nuevo conjunto de entrenamiento reducido está balanceado.
recuento_enfermedades<-summary(mini_training_dataset$condition)
enfermedades_matriz<-data.matrix(recuento_enfermedades)
enfermedades<-names(recuento_enfermedades)
recuento<-enfermedades_matriz[1:length(enfermedades_matriz)]
dataframe<-data.frame(enfermedades=enfermedades[1:10], recuento=recuento[1:10])
ggplot(data=dataframe, aes(x=enfermedades, y=recuento)) + geom_bar(stat="identity")+coord_flip()+ggtitle('Clases de enfermedades del conjunto de entrenamiento.')
```
Como podemos comprobar en la gráfica anterior, en este dataset reducido ya no hay clases mayoritarias ni minoritarias, si no que todas disponen del mismo número de muestras representativas. Por lo tanto, hemos conseguido obtener un conjunto de datos de entrenamiento equilibrado con 5.000 muestras, es decir, 500 muestras para cada etiqueta que representa cada una de las diez enfermedades más comunes.
Del mismo modo procedemos con el **conjunto de prueba** de modo que también cuente con hasta 10 enfermedades diferentes con 500 muestras para cada una de ellas. Para ello repetiremos el mismo proceso anterior.
```{r paged.print=TRUE, tidy=TRUE}
## CONJUNTO DE PRUEBA
# Obtenemos un resumen acerca de las enfermedades registradas en el dataset.
recuento_etiquetas<-summary(new_test_dataset$condition)
# Recopilamos los nombres de las enfermedades.
etiquetas<-names(recuento_etiquetas)
# Formamos un dataset más pequeño con las 10 enfermedades con mayor número de ejemplos y les asociamos 500 muestras para cada una.
n_enfermedades_max = 10
n_muestras_max = 500
# Primera enfermedad
mini_test_dataset <- head(subset(new_test_dataset, new_test_dataset$condition==etiquetas[1]), n_muestras_max)
# Resto de enfermedades
for (i in c(2:n_enfermedades_max)) {
mini_test_dataset <- rbind(mini_test_dataset, head(subset(new_test_dataset,
new_test_dataset$condition==etiquetas[i]), n_muestras_max))
}
# Desorganizamos los datos
set.seed(42)
rows<-sample(nrow(mini_test_dataset))
mini_test_dataset<-mini_test_dataset[rows,]
# Repetimos el proceso que se ha aplicado anteriormente para la obtención de ciertas estadísticas para comprobar que realmente el nuevo conjunto de entrenamiento reducido está balanceado.
recuento_enfermedades<-summary(mini_test_dataset$condition)
enfermedades_matriz<-data.matrix(recuento_enfermedades)
enfermedades<-names(recuento_enfermedades)
recuento<-enfermedades_matriz[1:length(enfermedades_matriz)]
dataframe<-data.frame(enfermedades=enfermedades[1:10], recuento=recuento[1:10])
ggplot(data=dataframe, aes(x=enfermedades, y=recuento)) + geom_bar(stat="identity")+coord_flip()+ggtitle('Clases de enfermedades del conjunto de prueba.')
```
## Preprocesamiento de datos textuales.
En este subapartado procedemos a aplicar diversas técnicas para preparar los datos textuales, en especial las *reviews* de los pacientes, para que posteriormente podamos aplicar ciertos algoritmos con mayor facilidad. En primer lugar vamos a crear los vectores de documentos a partir de la columna anteriormente mencionada. Para ello convertiremos cada una de las críticas de los pacientes en un documento y las almacenaremos en un vector común denominado *corpus* [2].
```{r message=FALSE, warning=FALSE, tidy=TRUE}
# Cargamos el paquete que nos permitirá aplicar ciertas operaciones sobre textos.
library("tm")
# Datos de entrenamiento.
# Obtenemos en forma de vector la columna de las reviews.
train_reviews<-as.vector(mini_training_dataset$review)
# Lo convertimos en un documento y lo almacenamos en el corpus que posteriormente crearemos.
train_reviews_corpus<-VectorSource(train_reviews)
train_reviews_corpus<-VCorpus(train_reviews_corpus)
# Datos de test.
test_reviews<-as.vector(mini_test_dataset$review)
test_reviews_corpus<-VectorSource(test_reviews)
test_reviews_corpus<-VCorpus(test_reviews_corpus)
```
\begin{figure}[h]
\centering
\includegraphics[width=0.75\textwidth]{Imagenes/review1.png}
\caption{Contenido de una review.}
\label{review1}
\end{figure}
Tal y como se puede observar, ahora cada una de las *reviews* de los pacientes se encuentra en un documento aparte, es decir, cada una de las opiniones de los pacientes ocupan una posición del vector de documentos. A continuación eliminaremos los **signos de puntuación** de las críticas porque, en principio, no nos aportan información relevante a la hora de aplicar técnicas relacionadas con la minería de textos. Asimismo, también hemos observado que ciertos signos de puntuación, como las comillas, no han sido procesados de forma adecuada y aparecen codificados como números. Obviamente este tipo de términos tampoco nos son útiles, por lo que procedemos, además, a **eliminar los números** de las críticas. Del mismo modo, con el objetivo de aplicar el mismo tratamiento a todas las palabras, procedemos también a **eliminar las mayúsculas**.
Por otro lado cabe destacar que para ciertas técnicas de análisis de textos, como podría ser encontrar los términos más comúnes, existen algunas palabras, en todos los idiomas, cuyo número de apariciones es súmamente considerable pero no aportan ningún tipo de información relevante. Este tipo de términos se pueden clasificar en preposiciones, artículos, entre otros, y en este ámbito se suelen conocer como **stop words**. En nuestro caso, como todas las *reviews* se encuentran en inglés, especificaremos dicho idioma para eliminar este tipo de términos de las críticas de los pacientes. Tras este procedimiento, los términos eliminados dejan espacios en blanco que también vamos a eliminar en este primer preprocesamiento de los datos textuales. Para todo este proceso utilizaremos la librería *tm* especializada para el análisis y preprocesamiento de textos, y en particular la función *tm_map* [3].
```{r, warning=FALSE, tidy=TRUE}
# Cargamos la librería que nos permitirá aplicar ciertas técnicas de preprocesamiento de textos.
library(tm)
# Datos de entrenamiento.
# Eliminamos los signos de puntuación con la función tm_map indicándoselo como segundo argumento.
train_reviews_corpus<-tm_map(train_reviews_corpus, content_transformer(removePunctuation))
# Eliminamos los números.
train_reviews_corpus<-tm_map(train_reviews_corpus, content_transformer(removeNumbers))
# Eliminamos las mayúsculas.
train_reviews_corpus<-tm_map(train_reviews_corpus, content_transformer(tolower))
# Eliminamos los stopwords en inglés.
train_reviews_corpus<-tm_map(train_reviews_corpus, content_transformer(removeWords), stopwords("english"))
# Eliminamos los espacios en blanco sobrantes tras el anterior procedimiento,
train_reviews_corpus<-tm_map(train_reviews_corpus, content_transformer(stripWhitespace))
# Datos de prueba.
test_reviews_corpus<-tm_map(test_reviews_corpus, content_transformer(removePunctuation))
test_reviews_corpus<-tm_map(test_reviews_corpus, content_transformer(removeNumbers))
test_reviews_corpus<-tm_map(test_reviews_corpus, content_transformer(tolower))
test_reviews_corpus<-tm_map(test_reviews_corpus, content_transformer(removeWords), stopwords("english"))
test_reviews_corpus<-tm_map(test_reviews_corpus, content_transformer(stripWhitespace))
```
\begin{figure}[h]
\centering
\includegraphics[width=0.75\textwidth]{Imagenes/review2.png}
\caption{Contenido preprocesado de una review.}
\label{review2}
\end{figure}
Como podemos observar, tras este primer preprocesamiento de las *reviews* de los pacientes se presentan textos en los que únicamente aparecen términos que tienen un mayor grado de relevancia para el estudio de este dataset en particular. No obstante, a consecuencia de eliminar palabras, pueden existir documentos sin contenido y por lo tanto no disponen de ninguna utilidad. Este será el siguiente procesamiento que aplicaremos a continuación: **eliminar los documentos vacíos.** [3].
```{r, tidy=TRUE}
# Datos de entrenamiento.
# Borraremos los documentos que estén vacíos. Para ello los tranformamos en una matriz de documentos.
train_reviews_matriz<-DocumentTermMatrix(train_reviews_corpus)
# Contamos el número de palabras de cada documento.
recuento_palabras<-apply(train_reviews_matriz, 1, sum)
# Comprobamos el número de documentos con 0 palabras.
cat("Documentos vacíos en el conjunto de entrenamiento:", recuento_palabras[recuento_palabras=0])
# Datos de prueba
test_reviews_matriz<-DocumentTermMatrix(test_reviews_corpus)
recuento_palabras<-apply(test_reviews_matriz, 1, sum)
cat("\nDocumentos vacíos en el conjunto de prueba", recuento_palabras[recuento_palabras=0])
# Estudio de la colección de documentos del conjunto de entrenamiento obtenida.
cat("\n\nEstudio de la colección de documentos del conjunto de entrenamiento tras el preprocesamiento.")
inspect(train_reviews_matriz)
```
Tal y como podemos comprobar, en nuestro caso, tras realizar el procedimiento por el cual eliminábamos palabras no relevantes, no existen documentos vacíos de contenido por lo que no podemos descartar ninguno. Asimismo, en función del estudio que se muestra a continuación acerca de la naturaleza de los documentos obtenidos del conjunto de entrenamiento, cabe destacar que se disponen de un total de 5.000 documentos y 11.825 términos. Sin embargo, en la lista de los términos más comunes podemos comprobar que algunos de ellos son variantes de otros, como por ejemplo son *take* y *taking*. Representa la misma información pero sin embargo se identifican como términos diferentes. Para eliminar este tipo de situaciones procedemos a aplicar una técnica conocida como **Lematización o Steaming**. Con ella se descartan aquellas palabras que tengan la misma raíz semántica y solo quedará un término de la misma familia [3].
```{r, warning=FALSE, tidy=TRUE}
# Datos de entrenamiento.
train_reviews_corpus<-tm_map(train_reviews_corpus, stemDocument)
train_reviews_matriz<-DocumentTermMatrix(train_reviews_corpus)
cat("Estudio de los documentos obtenidos tras el proceso de Lematización\n")
inspect(train_reviews_matriz)
# Datos de prueba.
test_reviews_corpus<-tm_map(test_reviews_corpus, stemDocument)
test_reviews_matriz<-DocumentTermMatrix(test_reviews_corpus)
```
Tras la aplicación de esta última técnica podemos comprobar que se ha producido una reducción razonable de términos pasando a tener menos de 8.500 en total. Asimismo, podemos observar una mejora en relación a los términos más frecuentes puesto que todos ellos son diferentes y por lo tanto todos pueden aportar información relevante a la hora de aplicar los sucesivos algoritmos a lo largo de este documento.
## Análisis de correlación.
En esta sección vamos a estudiar las relaciones que existen entre todas los campos de nuestro dataset para comprobar si algunos de ellos están correlacionados. Para ello vamos a convertir los atributos categóricos, como las enfermedades y los tratamientos, en valores numéricos sobre los que aplicar el estudio de la correlación tal y como se lleva a cabo en este tutorial [4]. Sin embargo, campos como la fecha en la que se publicó la crítica del paciente así como esta misma, los vamos a omitir puesto que no tiene sentido intentar convertirlos en variables numéricas.
```{r message=FALSE, warning=FALSE, tidy=TRUE}
# Copio el dataset original en otro que modificaremos para este apartado.
df_corr<-mini_training_dataset
# Eliminamos los campos categóricos puesto que vamos a convertir sus valores en valores numéricos.
df_corr$drugName <- NULL
df_corr$condition <- NULL
# También eliminamos los campos review y date puesto que no los vamos a utilizar en esta sección.
df_corr$review<-NULL
df_corr$date<-NULL
# Conversión de las variables categóricas, que son enfermedades y tratamientos, a variables numéricas.
df_corr["condition"] <- transform(as.numeric(mini_training_dataset$condition))
df_corr["drugName"] <- transform(as.numeric(mini_training_dataset$drugName))
# Calculo la matriz de correlación para las variables numéricas.
matriz_correlacion<-cor(df_corr)#, method="spearman")
# Calculamos los gráficos de dispersión y los coeficientes de correlación.
library(PerformanceAnalytics)
chart.Correlation(df_corr, histogram = F, pch = 19)
# Graficamos los coeficientes de correlación para todas las variables evaluadas.
library(corrplot)
corrplot(matriz_correlacion, method="number", type="upper")
```
En la primera imagen podemos comprobar los coeficientes de correlación entre cada par de variables existentes junto con la confianza asociada a esta correlación, representando la máxima con tres estrellas. Tal y como podemos comprobar, las variables que se encuentran más relacionadas son *rating* y *usefulCount* aunque su valor al ser de 0.24 no es considerablemente significante como para establecer una relación de correlación. Asimismo, si bien podemos pensar que los campos asociados a las enfermedades y a los tratamientos podrían mostrar una asociación, como podemos observar, esta no se hace patente a la hora de convertir los valores categóricos a numéricos puesto que de este modo el algoritmo no es capaz de relacionar ambos conceptos.
En la segunda imagen podemos visualizar los coeficientes de correlación entre cada par de variables clasificados y coloreados en función de cuán fuerte es su asociación. Tal y como podemos comprobar, la relación de correlación más fuerte, y por ende, la que dispone de un color más definido, es la que hemos comentado anteriormente: *rating-usefulCount*. El resto de asociaciones no cuentan con valores significativos y por lo tanto, apenas son visibles en la gráfica.
## Análisis de la influencia de las variables.
A continuación comprobaremos la importancia de cada uno de los campos que se incluyen en nuestro dataset. El objetivo es utilizar, solamente, aquellas columnas que sean relevantes para el estudio de este dataset en particular. Comenzamos eliminando directamente la columna *uniqueID* en ambos conjuntos de datos puesto que no aporta ningún tipo de información relevante ya que solo representa el identificador único para cada una de los registros del dataset.
Del mismo modo también vamos a eliminar el campo en el que se recoge la fecha en la que se publicó la crítica puesto que tampoco nos aporta información útil para aplicar los sucesivos algoritmos.
```{r, tidy=TRUE}
## CONJUNTO DE ENTRENAMIENTO
mini_training_dataset = mini_training_dataset[-1]
mini_training_dataset = mini_training_dataset[-5]
## CONJUNTO DE PRUEBA
mini_test_dataset = mini_test_dataset[-1]
mini_test_dataset = mini_test_dataset[-5]
```
# Análisis textual
Tal y como hemos comentado al comienzo de este documento, una de las columnas más relevantes de nuestro dataset es la denominada *review*, en la que los pacientes expresan su opinión acerca del tratamiento recetado para su dolencia en particular. Es por ello por lo que, tras preprocesar estas críticas, procedemos a analizar el contenido de las mismas con el objetivo de conocer cuáles son los términos más repetidos así como el contento o descontento de los pacientes con sus respectivos medicamentos.
## Nube de palabras
Una vez disponemos de los datos preprocesados, tanto asociados al conjunto de entrenamiento como al de prueba, procedemos a representar gráficamente los términos obtenidos de los diversos documentos mediante una primera **nube de palabras**, en la cual se ordenarán las palabras de las críticas de los pacientes en función de su número de apariciones. De este modo podremos conocer los términos más utilizados en las *reviews*. Estos serán representados en un mayor tamaño y se irá reduciendo conforme disminuya el número de veces que aparece cada uno de los términos. Para dibujar la nube de palabras utilizaremos la librería *wordcloud2* [5] que es capaz de realizar gráficos más interesantes y vistosos que el paquete wordcloud convencional.
```{r include=FALSE, message=FALSE, warning=FALSE, include=FALSE, results=FALSE, tidy=TRUE}
library(wordcloud2)
library(knitr)
# Convertimos el corpus en una matriz de documentos cuyas filas serán los términos que aparecen en cada uno de ellos.
train_reviews_matriz_terminos<-as.matrix(TermDocumentMatrix(train_reviews_corpus))
# Ordenamos los términos en función de sus apariciones totales.
frecuencia_terminos<-sort(rowSums(train_reviews_matriz_terminos), decreasing=TRUE)
# Obtenemos un dataframe de los términos ordenados para poder representar la nube de palabras.
train_reviews_dataframe_ordenado<-data.frame(word=names(frecuencia_terminos), freq=frecuencia_terminos)
# Almacenamos la nube de palabras en una imagen para poder mostrarla al completo en la memoria.
nube_palabras<-wordcloud2(train_reviews_dataframe_ordenado, size=1.2)
library("htmlwidgets")
saveWidget(nube_palabras,"nube_palabras.html",selfcontained = F)
library(webshot)
webshot("nube_palabras.html","nube_palabras.png",delay=20)
```
\begin{figure}[h]
\centering
\includegraphics[width=0.75\textwidth]{Imagenes/nube_palabras.png}
\caption{Nube de palabras.}
\label{nube_palabras}
\end{figure}
Tal y como podemos comprobar, la mayor parte de los términos más utilizados por los pacientes a la hora de expresar su opinión acerca de los fármacos recetados están directamente relacionados con el tiempo, como *day*, *year*, entre otros. Posteriormente, podemos observar un segundo grupo de palabras relacionadas con los sentimientos como *feel* o *pain*. Incluso podemos afirmar que dos de las enfermedades que se encuentran en nuestro dataset aparecen esta nube de palabras: *pain* y *anxieti*. Para estudiar más a fondo el contexto en el que se encuentran estos términos a continuación procedemos a analizar los sentimientos que se encuentran tras cada una de las críticas de los pacientes.
## Análisis de sentimientos
En esta sección trataremos de conocer el grado de satisfacción general de los pacientes con respecto a los tratamientos que se les han recetado para su dolencia particular. Para ello, en primer lugar, procedemos a realizar una análisis general que nos informe acerca de la tendencia global de las críticas tal y como se realiza en este estudio [6]. Utilizaremos el corpus extraido tras el preprocesamiento de las críticas y le aplicaremos el método **analyzeSentiment** de la biblioteca *SentimentAnalysis* [7] con la cual obtendremos el tipo de sentimiento que se esconde tras cada una de las *reviews*: positivo, negativo o neutro. Para ello este tipo de biblioteca contiene una serie de diccionarios con los términos más relevantes para cada uno de los tres tipos de sentimientos. En nuestro caso particular utilizaremos el diccionario por defecto denominado *QDAP*.
```{r message=FALSE, warning=FALSE, tidy=TRUE}
library(SentimentAnalysis)
# Obtenemos los sentimientos generales de las reviews
train_analisis_sentimientos<-convertToDirection(analyzeSentiment(train_reviews_corpus)$SentimentQDAP)
# Obtenemos su resumen estadístico
resumen_analisis = summary(train_analisis_sentimientos)
# Dibujamos los resultados obtenidos
barplot(resumen_analisis, main = "Análisis de sentimientos de las reviews en train",
xlab = "Tipo de sentimiento", ylab = "Nº de reviews", col = c("red3", "yellow2", "springgreen3"))
```
Como podemos observar en ambos gráficos la gran mayoría de las críticas muestran un **sentimiento positivo**, por lo que podemos deducir que de forma general los pacientes están satisfechos con los tratamientos que han sido recetados para sus respectivas enfermedades. Asimismo, cabe destacar la considerable diferencia entre el número de críticas positivas y negativas. Por último, las críticas asociadas a un sentimiento neutro se corresponden con el grupo minotiratio puesto que es entendemos que es bastante complicado proporcionar una opinión sin demostrar nuestros sentimientos acerca de un fármaco.
A continuación procedemos a profundizar más acerca de los resultados obtenidos anteriormente con el objetivo de conocer cuáles son los sentimientos concretos más comunes que los pacientes demuestran a la hora de opinar sobre sus respectivos tratamientos. Parar ello haremos uso de la librería *syuzhet*, y en concreto de la función *get_nrc_sentiment*, con la cual seremos capaces de medir el número de *reviews* que reflejan alguno de los ocho sentimientos posibles: enfado, expectación miedo, alegría, tristeza, confianza, sorpresa.
```{r, warning=FALSE, tidy=TRUE}
library(syuzhet)
library("tm")
library(ggplot2)
# Obtenemos el corpus de los comentarios de los pacientes ya preprocesados.
train_reviews_corpus2<-Corpus(VectorSource(train_reviews))
# Realizamos este nuevo análisis de sentimientos.
analisis_sentimientos2<-get_nrc_sentiment(train_reviews_corpus2$content)
# Representamos gráficamente los resultados ordenados de menor a mayor en función del porcentaje de críticas que se corresponden con cada sentimiento
library(RColorBrewer)
colores<-brewer.pal(8, "Set2")
barplot(sort(colSums(prop.table(analisis_sentimientos2[, 1:8]))), las=2, col=colores)
```
Tal y como podemos comprobar el sentimiento que predomina en las críticas de los pacientes es la confianza, seguido de un segundo sentimiento negativo que es la tristeza y de un tercero que es la expectación. Estos resultados pueden explicar el comportamiento de los seres humanos ante una enfermedad, y es que al ser diagnosticados es normal que sintamos tristeza por la situación actual y por la temporada que nos espera de tratamiento. Asimismo, también es razonable sentir confianza en los fármacos recetados por los especialistas en medicina para tratar nuestra dolencia y que nos hagamos ciertas expectativas acerca de la efectividad del mismo.
# Reglas de asociación
En esta técnica se pretende realizar un análisis acerca de uno de los campos más relevantes del dataset, a nuestro parecer: las *reviews* de los pacientes. Aplicando este procedimiento sobre nuestro conjunto de datos pretendemos extraer las relaciones existentes entre las palabras contenidas en estas críticas que redactan los enfermos asociadas a una determinada enfermedad y a uno o varios tratamientos. De este modo podremos conocer las relaciones existentes entre los términos que son más comunes en las *reviews*.
Inspirándonos en este estudio [8], el primer paso que procedemos a realizar consiste en separar los términos de las críticas de modo que obtengamos las palabras individuales y separadas del resto. Para ello haremos uso de la función *text_tokens* aplicada al corpus creado y preprocesado anteriormente. A continuación podremos componer la **base de datos transaccional** en la que cada elemento se corresponde con una única palabra. Una vez dispongamos de los datos preparados podremos obtener las reglas de asociación mediante el paquete ***aRules***. Para ello utilizaremos el algoritmo *apriori*[9], el cual extrae los conjuntos de palabras más frecuentes y los convierte en reglas de asociación. Como parámetros recibe la base de datos con las transacciones además de argumentos de configuración tales como el **soporte**, el cual indica el número de tuplas que soportan el proceso de inducción, y la **confianza**, la cual representa la calidad de cada una de las reglas. Además, para que sea aún más eficiente también estableceremos ciertos parámetros de rendimiento para que no cargue en memoria todas las reglas que obtenga. Asimismo también le indicamos que comience a anaizar reglas con dos términos mínimo.
Según esta fuente [10], para ajustar el primer valor deberemos de conocer el número de transacciones contenidas en la base de datos así como las frecuencias máximas de los términos recogidos en las *reviews*.
```{r message=FALSE, warning=FALSE, tidy=TRUE}
# Importamos la librería para obtener los términos del corpus separados.
library(arules)
library(corpus)
# Separamos los términos de cada una de las críticas ya preprocesadas.
terminos_criticas<-text_tokens(train_reviews_corpus)
# Convertimos los términos a una base de datos transaccional
reviews_bd_transaccional<-as(terminos_criticas, "transactions")
# Obtenemos un resumen acerca de la bd creada.
cat("\n\nResumen de la base de datos transaccional.\n")
summary(reviews_bd_transaccional)
cat("\n\nFrecuencia de las transacciones.\n")
summary(itemFrequency(reviews_bd_transaccional))
```
Como podemos observar existen 5.000 transacciones dentro de la base de datos obtenida. El término más repetido es *take* con hasta 2187 apariciones y su respectiva frecuencia es de 0.437, la máxima de entre todos los términos. Este valor nos indica el soporte máximo que podemos establecer. Sin embargo, para poder calcular este valor de una forma más precisa necesitamos establecer el número mínimo de apariciones que deseamos para cada uno de los términos y, posteriormente, lo dividimos entre el número total de transacciones. Comenzaremos estableciendo una frecuencia mínima de 500, por lo que el `soporte = 500/5000 = 0.1`. Establecemos el valor por defecto para la confianza: 0.8.
```{r message=FALSE, warning=FALSE, tidy=TRUE}
# Importamos la librería para obtener las reglas de asociación y la base de datos transaccional
library(arules)
library(arulesViz)
# Obtenemos las reglas de asociación.
reglas_asoc<-apriori(reviews_bd_transaccional, parameter = list(support = 0.1, confidence = 0.8, target="rules", minlen=2), control = list(memopt = TRUE, load = FALSE))
# Ordenamos las reglas obtenidas ordenadas por la confianza.
reglas_asoc<-sort(reglas_asoc, decreasing=TRUE, by="confidence")
# Dibujamos las reglas.
plot(reglas_asoc, method="graph")
```
Como podemos observar hemos obtenido un total de ocho reglas de asociación, en las que los dos consecuentes más comunes son *side* y *effect*. Esto se debe a que, de forma general, los pacientes tienden a expresar en sus *reviews* los efectos que les producen sus tratamientos. Sin embargo, con un soporte tan alto apenas obtenemos más información, por lo que a continuación estableceremos una frecuencia mínima de 50 apariciones para cada término, obteniendo así un **soporte=0.01**.
```{r, warning=FALSE, tidy=TRUE}
library(arules)
library(arulesViz)
reglas_asoc<-apriori(reviews_bd_transaccional, parameter = list(support = 0.01, confidence = 0.8, target="rules", minlen=2), control = list(memopt = TRUE, load = FALSE))
reglas_asoc<-sort(reglas_asoc, decreasing=TRUE, by="confidence")
plot(reglas_asoc, method="graph")
```
Al disminuir el soporte hemos obtenido un mayor número de información puesto que las reglas obtenidas son menos estrictas a la hora de buscar asociaciones entre los términos de las *reviews* de los pacientes. En este caso podemos observar un mayor número de relaciones entre palabras, como *gain* y *weight*, los cuales están estrechamente relacionados puesto que una de las enfermedades que se encuentra en nuestro dataset es precisamente la pérdida de peso, por lo que es lógico que aparezcan estos dos términos. Del mismo modo ocurre con *birth* y *control*. Asimismo, se ha añadido un consecuente más al núcleo de las reglas de asociación: *take*. Este hecho se explica en relación a que el paciente además de expresar los efectos que tienen los fármacos sobre su dolencia, por lo general también suelen especificar las tomas que realizan de este medicamento durante el tratamiento impuesto por su médico. Es por ello por lo que estos tres términos se encuentran como los consecuentes más comunes de las reglas obtenidas. Relacionado con este aspecto se encuentran los términos asociados con espacios temporales, como *week*, *now*, *month*, entre otros, los cuales aparecen también cercanos al núcleo.
A continuación procedemos a experimentar con el valor de la **confianza**. Si bien este mide la calidad de las reglas obtenidas, podemos deducir que a menor valor mayor número de reglas de peor calidad. Para ello vamos a realizar un primer experimento con soporte=0.1. Nuestra teoría parte de que si disminuimos la calidad de las reglas que se puedan obtener, aparecerán un mayor número de ellas pese al soporte establecido.
```{r, warning=FALSE, tidy=TRUE}
library(arules)
library(arulesViz)
reglas_asoc<-apriori(reviews_bd_transaccional, parameter = list(support = 0.1, confidence = 0.4, target="rules", minlen=2), control = list(memopt = TRUE, load = FALSE))
reglas_asoc<-sort(reglas_asoc, decreasing=TRUE, by="confidence")
plot(reglas_asoc, method="graph")
```
Tal y como podemos comprobar, en este caso se han podido obtener 52 reglas frente a las ocho que se obtuvieron con un valor de confianza del doble que el actual. No obstante, la calidad de estas reglas disminuye, es decir, las asociaciones de los términos pueden no ser del todo lógicas, como por ejemplo *ive* y *im*, que no aportan ningún tipo de información relevante. Es por ello, por lo que en consecuencia, podemos afirmar que un valor razonablemente alto de la confianza puede inducir a obtener relaciones entre términos que nos aporten información útil, mientras que un soporte demasiado elevado puede provocar cierta pérdida de información al ser tan estrictos con el número de apariciones de cada término.
# Técnicas de clasificación.
La clasificación es un proceso consistente en aprender una determinada función con el objetivo, generalmente, de realizar predicciones futuras. De este modo se entrena un clasificador aportando un conjunto de datos de entrenamiento así como las etiquetas asociadas para que ajuste sus pesos y, posteriormente, sea capaz de predecir las etiquetas de un conjunto de prueba nunca visto anteriormente. En esta sección se entrenarán diferentes clasificadores a partir de la aplicación de las técnicas más populares. Nuestro objetivo principal se encuentra inspirado por el reto de *Kaggle*[1] en el cual se propone **predecir la enfermedad del paciente en función de su comentario**. Por ello las sucesivas técnicas que se detallan a continuación han sido aplicadas sobre el texto correspondiente a las *reviews* de los pacientes ya preprocesados.
## Clasificación Bayesiana
Es un método probabilístico que determina la clase más probable a la que pertenece un elemento aplicando, para ello, el teorema de Bayes. Es comunmente utilizado cuando existe una componente aleatoria que provoca que dos elementos iguales estén categorizados en clases diferentes. Este aspecto encaja bastante bien con las críticas de los pacientes puesto que si bien sus términos pueden ser parecidos, en realidad sendos pacientes pueden sufrir dolencias diferentes.
Para aplicar esta técnica nos hemos basado en estos dos tutoriales [11, 12]. Como primer paso se deberá asignar a cada una de las enfermedades disponibles, tanto en el conjunto de entrenamiento como en el de test, una etiqueta numérica unívoca, que sea capaz de identificarla de forma individual. Para ello utilizamos la función *factor* tal y como se muestra en este tutorial [13], la cual realizará este mismo proceso descrito obteniendo mediante *as.numeric* las etiquetas numéricas del 1 al 10 asociadas a cada una de las enfermedades de ambos dataset. Tras visualizar el resultado obtenido podemos afirmar que estas etiquetas numéricas se asignan de menor a mayor en función del orden alfabético, por lo que las dolencias que comiencen por *A* dispondrán de los primeros valores.
A continuación obtenemos la matriz de documentos que contiene las *reviews* de los pacientes, como hemos realizado en anteriores técnicas, solo que en este caso solo nos vamos a quedar con aquellos con una frecuencia mínima de cinco apariciones. De este modo reducimos el número de palabras y consideraremos solo las más relevantes. Por último entrenamos el clasificador con el conjunto de entrenamiento y las etiquetas asociadas a las dolencias y mediremos su bondad a través de la tasa de error y de la matriz de confusión.
```{r message=FALSE, warning=FALSE, tidy=TRUE}
library(tm)
library(e1071)
# Asignamos a cada enfermedad diferente una etiqueta de 0 a 10 y la añadimos como una columna más a ambos datasets.
trainNCondition<-as.numeric(factor(mini_training_dataset$condition))
testNCondition<-as.numeric(factor(mini_test_dataset$condition))
mini_training_dataset<-cbind(trainNCondition, mini_training_dataset)
mini_test_dataset<-cbind(testNCondition, mini_test_dataset)
# Obtenemos la matriz de los términos de las reviews de los conjuntos de entrenamiento y test.
train_reviews_matriz<-DocumentTermMatrix(train_reviews_corpus)
test_reviews_matriz<-DocumentTermMatrix(test_reviews_corpus)
# Nos quedamos con los términos más frecuentes (con un mínimo número de aparciciones de 5)
terminos_frecuentes<-findFreqTerms(train_reviews_matriz, 5)
# Obtenemos de nuevo la matriz con los términos más frecuentes extraidos anteriormente tanto para train como para test.
train_reviews_frec_matriz<-DocumentTermMatrix(train_reviews_corpus, control=list(dictionary=terminos_frecuentes))
test_reviews_frec_matriz<-DocumentTermMatrix(test_reviews_corpus, control=list(dictionary=terminos_frecuentes))
# Función que transforma la frecuencia de los términos a una clase binaria: están o no están.
frecuencia_binaria<-function(x) { y<-ifelse(x > 0, "Yes", "No") }
# Obtenemos la matriz de los términos definiendo para cada uno si pertenece a la clase "No" (no está) o a la clase "Yes" (sí aparece)
train_reviews_matriz_bin<-apply(train_reviews_frec_matriz, 2, frecuencia_binaria)
test_reviews_matriz_bin<-apply(test_reviews_frec_matriz, 2, frecuencia_binaria)
# Entrenamos el modelo con Naive Bayes binario
clasificador_naive<-naiveBayes(train_reviews_matriz_bin, as.factor(mini_training_dataset$trainNCondition), laplace = 1)
# Obtenemos las predicciones que hace el modelo sobre el conjunto de entrenamiento y de test.
train_pred<-predict(clasificador_naive, train_reviews_matriz_bin)
test_pred<-predict(clasificador_naive, test_reviews_matriz_bin)
# Calculamos sus errores
error_train<-mean(train_pred!=mini_training_dataset$trainNCondition)
error_test<-mean(test_pred!=mini_test_dataset$testNCondition)
# Mostramos los datos más relevantes.
cat("Orden de asignación de etiquetas 1-10\n")
cat(levels(factor(mini_training_dataset$condition)))
test_matriz_confusion_naive<-table(test_pred, mini_test_dataset$testNCondition)
cat("\n\nError en train: ", error_train*100,"% \nError en test: ",error_test*100,"%")
cat("\n\nMatriz de confusión en test\n")
print(test_matriz_confusion_naive)
```
Tal y como podemos comprobar ambas tasas de error no son competitivas pero sí son razonablemente aceptables, teniendo en cuenta que el problema de clasificación planteado no es nada sencillo, puesto que el clasificador debe elegir una de las diez enfermedades posibles basándose solo en el contenido de las críticas preprocesadas de los pacientes. Estas, en la gran mayoría de ocasiones, no permiten al cien por cien conocer cuál es la dolencia a la que se enfrenta dicha persona puesto que suelen estar más enfocadas a la opinión suministrada en función de la efectividad del medicamento más que de la descripción de la propia enfermedad.
Si analizamos la **matriz de confusión** podemos comprobar como en la mayoría de los casos no existe un número muy pronunciado de falsos positivos, exceptuando en la etiqueta 8, cuya enfermedad asociada *Insomnia* es bastante confundida con la relacionada a la etiqueta 10 que es *Weight Loss*, y viceversa. Esto nos lleva a pensar que los comentarios de los pacientes que padecen ambas enfermedades utilizan términos muy parecidos que confunden al clasificador.
## K-NN
Se trata de una de las técnicas predictivas que se encuentran dentro de la clasificación basada en instancias. Este algoritmo denominado **K-vecinos más cercanos** selecciona los *k* elementos más parecidos a un determinado ejemplo y les asigna la clase más frecuente. Si bien este método está pensado para trabajar con variables numéricas, vamos a aplicarlo de nuevo a la predicción de la enfermedad en base a las *reviews* de los pacientes, como con la técnica anterior.
Comenzamos esta sección probando la librería *kknn* vista en clase para entrenar un clasificador utilizando la técnica que nos ocupa en cuestión. El proceso que llevamos a cabo fue muy similar al anterior, exceptuando la extracción de los términos más comunes, es decir, directamente probamos a entrenar el clasificador con todos los términos de las *reviews* del conjunto de entrenamiento. Sin embargo, el rendimiento fue súmamente diferente puesto que obtuvimos una **tasa de error de un 99%**. Intentamos mejorar este resultado pero no encontramos ejemplos en internet en los que utilizasen esta librería en concreto aplicada a texto, si no que hacían uso de una denominada *class* [13]. Por tanto, decidimos probar suerte con la función *knn* almacenada en esta biblioteca inspirándonos, para ello, en este tutorial [14]. Al intentar entrenar el clasificador con el conjunto de entrenamiento y validarlo con el conjunto de test nos arrojaba un error acerca de las dimensiones diferentes que tenían las matrices de términos de sendos conjuntos, y es que si bien disponen del mismo número de filas, en cada conjunto de datos hay un número diferente de términos, por lo que las columnas no coincidían. Para solucionar este problema el tutorial mencionado anteriormente propone utilizar la técnica de **validación cruzada** para dividir el conjunto de entrenamiento en un conjunto para entrenar y otro para validar. De este modo nos aseguramos que en sendos conjuntos se encuentran todos los términos. De esta forma sí hemos sido capaces de entrenar un clasificador con esta técnica.
```{r message=FALSE, tidy=TRUE}
library(tm)
library(dplyr)
library(class)
# Obtenemos la matriz de términos del conjunto de entrenamiento.
train_reviews_matriz<-DocumentTermMatrix(train_reviews_corpus)
# Obtenemos los términos más frecuentes.
terminos_frecuentes_train<-findFreqTerms(train_reviews_matriz, 5)
# Obtenemos la nueva matriz de términos más frecuentes.
train_reviews_frec_matriz<-DocumentTermMatrix(train_reviews_corpus, control=list(dictionary=terminos_frecuentes_train))
# Convertimos a dataframe el conjunto de datos de entrenamiento para pasárselos al algoritmo.
train_df<-as.data.frame(data.matrix(train_reviews_frec_matriz), stringsAsfactors = FALSE)
# Añadimos la columna de clasificación de las enfermedades.
train_df<-cbind(train_df, trainNCondition)
# Obtenemos las etiquetas de las enfermedades para entrenar el clasificador.
etiquetas<-train_df[,"trainNCondition"]
# Obtenemos el modelo con el que vamos a entrenar el clasificador sin la columna de etiquetas, puesto que se la pasaremos de forma individual como tercer parámetro.
modelo<-train_df[,!colnames(train_df) %in% "trainNCondition"]
# Validación cruzada para dividir el conjunto de train en un conjunto de entrenamiento y otro de validación.
train<-sample(nrow(train_df), ceiling(nrow(train_df) * .70))
test<-(1:nrow(train_df))[- train]
# Obtenemos las predicciones del conjunto de validación
knn.pred<-knn(modelo[train, ], modelo[test, ], etiquetas[train], k=1)
# Obtenemos la matriz de confusión y la tasa de error
m_conf<-table("Predicciones" = knn.pred, Real = etiquetas[test])
m_conf
test_error<-100-(sum(diag(m_conf))/length(test) * 100)
cat("\nError en el conjunto de prueba:",test_error,"%")
```
Tal y como podemos comprobar, con `k=1`, es decir, considerando un único vecino más cercano la **tasa de error sobre el conjunto de prueba es mayor del 60%**. Si analizamos la matriz de confusión generada podemos observar que en la mayoría de los errores se concentran en torno a las enfermedades cuyas etiquetas asociadas son 2, 3, 6, 7 y 10. Esto significa que el clasificador no es capaz de distinguir correctamente entre estas dolencias en base a las *reviews* de los pacientes. En base a estos resultados podemos determinar que con esta configuración, el clasificador entrenado no dispone de una buena capacidad de generalización y por ello falla en más de la mitad de ocasiones. Investigando acerca del valor óptimo para k, es decir, encontrar el número de vecinos adecuado a considerar encontramos una idea en esta fuente [15]. Consiste en entrenar varios clasificadores mediante la técnica KNN utilizando validación cruzada y distintos valores para *k*. De este modo compara los resultados obtenidos en todos los casos y nos mostrará aquel valor de *k* para el cual se obtiene un mejor resultado. Como este procedimiento es computacionalmente muy costoso, solo lo hemos ejecutado una vez y tras el siguiente código se proporcionará la captura con los resultados obtenidos.
```{r, eval=FALSE, tidy=TRUE}
library(caret)
set.seed(400)
# Utiliza validación cruzada para probar diversos valores de k
ctrl<-trainControl(method="repeatedcv", repeats=3)
knnFit<-train(trainNCondition ~ ., data=train_df, method="knn", trControl=ctrl, preProcess=c("center","scale"))
knnFit
```
\begin{figure}[h]
\centering
\includegraphics[width=0.75\textwidth]{Imagenes/k_knn.png}
\caption{Validación cruzada para encontrar el k óptimo.}
\label{k_optimo}
\end{figure}
Tal y como podemos observar, de entre todos los experimentos realizados el clasificador entrenado con **k=5** es el que mejor precisión ha obtenido. A continuación repetiremos el mismo proceso anterior solo que en lugar de k=1 establecemos el nuevo valor óptimo descubierto para esta variable.
```{r, tidy=TRUE}
# Importamos la librería con la que aplicaremos el algoritmo KNN.
library(class)
# Obtenemos las predicciones del conjunto de validación
knn.pred<-knn(modelo[train, ], modelo[test, ], etiquetas[train], k=5)
# Obtenemos la matriz de confusión y la tasa de error
m_conf<-table("Predicciones" = knn.pred, Real = etiquetas[test])
m_conf
test_error<-100-(sum(diag(m_conf))/length(test) * 100)
cat("\nError en el conjunto de prueba:",test_error,"%")
```
Tal y como se puede comprobar el clasificador entrenado, considerando los cinco vecinos más cercanos, dispone de una **tasa de error sobre el conjunto de prueba también superior al 60%**, aunque menor que en el caso anterior. Si bien ha conseguido mejorar un poco con respecto al resultado anterior con k=1, podemos confirmar que esta técnica no nos aporta un clasificador capaz de predecir la enfermedad en función de las *reviews* de los pacientes, por lo que no es el mejor algoritmo para aplicarlo a nuestros datos. Si analizamos la matriz de confusión, podemos observar que la mayoría de errores se concentran a partir de la segunda etiqueta. Esto significia que el clasificador no es capaz de distinguir estas enfermedades de forma correcta en base a las *reviews* de los pacientes.
Posteriormente, para asegurarnos que realmente el valor de k óptimo es 5 hicimos una serie de pruebas variando el valor de este, que no vamos a adjuntar en este documento puesto que esta técnica es muy costosa computacionalmente de aplicar. Sin embargo, pudimos confirmar que **k=5** es el mejor valor que podemos establecer para esta técnica puesto que ni con k=3, con el que obtuvimos un error de 64%, ni con k=10 que proporcionaba un error 63.73%, obtuvimos una tasa de error menor.
## Árboles de decisión
Los árboles de decisión se utilizan para la clasificación debido a su facilidad tanto de interpretación de los resultados como de la extracción de las reglas que ayudan a entrenar un clasificador. Se trata de un algoritmo muy potente y especialmente dedicado a trabajar con dataset complejos y con un gran número de datos. Es por ello por lo que, como con técnicas anteriores, vamos a aplicarla a las *reviews*. El objetivo sigue siendo el mismo: intentar predecir las enfermedades en base a las críticas de los pacientes ya preprocesadas.
Para ello hemos investigado un poco acerca del proceso a seguir para entrenar un clasificador mediante árboles aplicado a texto y según hemos podido ver en esta fuente [16] el procedimiento es muy similar al de la técnica de Naive Bayes. En primer lugar obtenemos las matrices de términos así como la frecuencia de los mismos tanto para el conjunto de entrenamiento como para el de prueba. Estos valores serán los que se tendrá en cuenta a la hora de buscar indicios en las *reviews* que nos permitan predecir a qué enfermedad están haciendo alusión. Por último convertimos ambas matrices a dataframe con los que poder trabajar y les añadimos las respectivas columnas de clasificación de las enfermedades a sendos conjuntos de datos.
Para entrenar el clasificador vamos a hacer uso de la librería *rpart* vista en clase y calcularemos tanto la matriz de confusión como las tasas de error tanto en entrenamiento como en validación.
```{r, warning=FALSE, tidy=TRUE}
library(tm)
library(rpart)
# Obtenemos la matriz de los términos de las reviews de los conjuntos de entrenamiento y test.
train_reviews_matriz<-DocumentTermMatrix(train_reviews_corpus)
test_reviews_matriz<-DocumentTermMatrix(test_reviews_corpus)
# Nos quedamos con los términos más frecuentes (con un mínimo número de aparciciones de 5)
terminos_frecuentes<-findFreqTerms(train_reviews_matriz, 5)
# Obtenemos de nuevo la matriz con los términos más frecuentes extraidos anteriormente tanto para train como para test.
train_reviews_frec_matriz<-DocumentTermMatrix(train_reviews_corpus, control=list(dictionary=terminos_frecuentes))
test_reviews_frec_matriz<-DocumentTermMatrix(test_reviews_corpus, control=list(dictionary=terminos_frecuentes))
# Convertimos a dataframe el conjunto de datos de entrenamiento para pasárselos al algoritmo.
train_freq<-as.data.frame(data.matrix(train_reviews_frec_matriz), stringsAsfactors = FALSE)
test_freq<-as.data.frame(data.matrix(test_reviews_frec_matriz), stringsAsfactors = FALSE)
# Añadimos la columna de clasificación de enfermedades al conjunto de test para luego
# realizar la predicción y evaluar el clasificador.
train_freq<-cbind(train_freq, trainNCondition)
test_freq<-cbind(test_freq, testNCondition)
# Entrenamos el modelo árboles de decisión.
clasificador_arboles<-rpart(trainNCondition~., train_freq, method="class")
# Mostramos los resultados.
printcp(clasificador_arboles)
# Calculamos el error y la matriz de confusión para entrenamiento.
train_predd<-predict(clasificador_arboles, train_freq, type="class")
train_confusion<-table(train_freq$trainNCondition, train_predd)
error_train<-sum(diag(train_confusion))/nrow(train_freq)
# Calculamos el error y la matriz de confusión para test.
test_predd<-predict(clasificador_arboles, test_freq, type="class")
test_confusion<-table(test_freq$testNCondition, test_predd)
test_confusion
error_test<-sum(diag(test_confusion))/nrow(test_freq)
cat("\nError en entrenamiento:",error_train*100,"% \nError en test:",error_test*100,"%")
```
Tal y como podemos observar las tasas de error son bastante altas, por lo que el clasificador no dispone de una buena capacidad de generalización, y por tanto, no le permite diferenciar qué enfermedad está asociada a cada *review*. Si además observamos la matriz de confusión con respecto al conjunto de test, podemos visualizar que la mayoría de falsos positivos se encuentran a partir de la etiqueta 2, por lo que a partir de la segunda dolencia ya no es capaz de diferenciarlas claramente. Continuamos investigando cómo mejorar el rendimiento de este tipo de clasificador y encontramos varios tutoriales como este [17], en el que explican una serie de parámetros de control para poder ajustar el entrenamiento del clasificador [18]. El primero de ellos es *minsplit* con el cual le indicaremos el número mínimo de registros que debe tener un nodo para poder dividirse. Hemos probado con diversos valores y hemos comprobado que a mayor valor, peor tasa de error en sendos conjuntos. La conclusión a la que llegamos es que si establecemos un valor demasiado alto esto no permitirá dividir el nodo en más caminos y por lo tanto podemos perder la solución óptima.
Otro valor importante es *maxdepth*, con el que establecemos la profundidad máxima del árbol. Del mismo modo también hemos variado este valor y en función de los resultados así como del costo computacional, el mejor valor para nuestro dataset es que el árbol tenga profundidad 3.
```{r, tidy=TRUE}
# Entrenamos el modelo árboles de decisión.
control<-rpart.control(minsplit=4, maxdepth=3)
clasificador_arboles<-rpart(trainNCondition~., train_freq, method="class", control=control)
# Calculamos el error y la matriz de confusión para entrenamiento.
train_predd<-predict(clasificador_arboles, train_freq, type="class")
train_confusion<-table(train_freq$trainNCondition, train_predd)
error_train<-sum(diag(train_confusion))/nrow(train_freq)
# Calculamos el error y la matriz de confusión para test.
test_predd<-predict(clasificador_arboles, test_freq, type="class")
test_confusion<-table(test_freq$testNCondition, test_predd)
test_confusion
error_test<-sum(diag(test_confusion))/nrow(test_freq)
cat("\nError en entrenamiento:",error_train*100,"% \nError en test:",error_test*100,"%")
plotcp(clasificador_arboles)
```
Tal y como podemos obsevar, ambas tasas de error se han reducido considerablemente hasta alcanzar niveles más normalizados. Asimismo, la matriz de confusión respecto al conjunto de prueba también ha mejorado considerablemente puesto que, tal y como se puede apreciar, el número de confusiones y enfermedades mal etiquetadas es mucho menor. Si observamos, a continuación, el gráfico que muestra la evolución del aprendizaje del árbol podemos observar que el valor óptimo para **cp=0.023**, es decir, si establecemos este valor conseguiremos que el rendimiento del clasificador sea óptimo así como su tasa de acierto puesto que no continuará explorando un camino que no mejore más allá de dicho valor. A continuación entrenamos un tercer clasificador fijando este parámetro.
```{r, tidy=TRUE}
# Entrenamos el modelo árboles de decisión.
control<-rpart.control(minsplit=4, maxdepth=3, cp=0.023)
clasificador_arboles<-rpart(trainNCondition~., train_freq, method="class", control=control)
# Calculamos el error y la matriz de confusión para entrenamiento.
train_predd<-predict(clasificador_arboles, train_freq, type="class")
train_confusion<-table(train_freq$trainNCondition, train_predd)
error_train<-sum(diag(train_confusion))/nrow(train_freq)
# Calculamos el error y la matriz de confusión para test.
test_predd<-predict(clasificador_arboles, test_freq, type="class")
test_confusion<-table(test_freq$testNCondition, test_predd)
test_confusion
error_test<-sum(diag(test_confusion))/nrow(test_freq)
cat("\nError en entrenamiento:",error_train*100,"% \nError en test:",error_test*100,"%")
```
Tal y como podemos observar, se ha vuelto a reducir un poco ambos errores. Como conclusiones para terminar esta sección podemos afirmar que si bien hemos conseguido mejorar su rendimiento de forma considerable, esta técnica no proporciona buenos resultados en relación a nuestro objetivo principal, que era predecir enfermedades en función de las *reviews* de los pacientes.
## Random Forest
Esta técnica se presenta como una variante de otra denominada *Bagging* por la cual se introduce cierta aleatoriedad con el objetivo de mejorar la clasificación de los elementos dado un dataset. Para ello genera un gran número de árboles independientes entre sí y posteriormente combina sus resultados realizando un promedio entre todos ellos para etiquetar a cada uno de los elementos. A continuación, procedemos a aplicar este algoritmo como lo venimos haciendo con las anteriores técnicas de clasificación, pues nuestro propósito es averiguar cuál es la mejor técnica de clasificación que proporciona unos buenos resultados prediciendo la enfermedad a partir de la *review* del paciente.
Para entrenar un clasificador con esta técnica vamos a hacer uso de la librería *randomForest* vista en clase, en particular con la función de igual nombre **randomForest**[19]. Más concretamente, procedemos a utilizar la versión de dicha función en la cual se puede expresar la fórmula que definirá el modelo que deseamos entrenar. En un primer intento encontramos un error debido a que la matriz de términos del conjunto de entrenamiento y la del conjunto de prueba no disponen de los mismos términos, y por tanto, el clasificador no puede obtener las predicciones de términos que no ha visto préviamente. Es por ello por lo que, en un segundo intento, aplicamos de nuevo la **validación cruzada** para dividir nuestro conjunto de entrenamiento original en un conjunto de *train* y otro de *test* para validar, posteriormente, el clasificiador entrenado.
```{r message=FALSE, tidy=TRUE}
library(tm)
# Obtenemos la matriz de términos del conjunto de entrenamiento.
train_reviews_matriz<-DocumentTermMatrix(train_reviews_corpus)
# Obtenemos los términos más frecuentes.
terminos_frecuentes_train<-findFreqTerms(train_reviews_matriz, 5)
# Obtenemos la nueva matriz de términos más frecuentes.
train_reviews_frec_matriz<-DocumentTermMatrix(train_reviews_corpus, control=list(dictionary=terminos_frecuentes_train))
# Convertimos a dataframe el conjunto de datos de entrenamiento para pasárselos al algoritmo.
train_df<-as.data.frame(data.matrix(train_reviews_frec_matriz), stringsAsfactors = FALSE)
# Añadimos la columna de clasificación de las enfermedades.
train_df<-cbind(train_df, trainNCondition)
# Modificamos el nombre a las columnas para que el algoritmo pueda trabajar con los datos
colnames(train_df) <- paste(colnames(train_df), "_c", sep = "")
# Validación cruzada para dividir el conjunto de train en un conjunto de entrenamiento y otro de validación.
train_vc<-train_df[1:3500, ]
test_vc<-train_df[3501:5000, ]
# Importamos la librería con la que aplicaremos la técnica Random Forest
library(randomForest)
# Entrenamos el clasificador con un máximo de 100 árboles.
clasificador_rf<-randomForest(formula=train_vc$trainNCondition_c~., data=train_vc, ntree=100)
# Calculamos el error y la matriz de confusión para entrenamiento.
train_predd<-predict(clasificador_rf, train_vc, type="response")
train_confusion<-table(train_vc$trainNCondition_c, train_predd)
error_train<-sum(diag(train_confusion))/nrow(train_vc)
# Calculamos el error y la matriz de confusión para test.
test_predd<-predict(clasificador_rf, test_vc, type="response")
test_confusion<-table(test_vc$trainNCondition_c, test_predd)
error_test<-sum(diag(test_confusion))/nrow(test_vc)
clasificador_rf
cat("\nError en entrenamiento:",error_train*100,"% \nError en test:",error_test*100,"%")
```
Como podemos comprobar el algoritmo ha aplicado regresión para poder entrenar el clasificador y así determinar si una determinada *review* hace referencia a una enfermedad u a otra. En base a las tasas de error obtenidas podemos determinar que el clasificador entrenado dispone de una muy buena capacidad de generalización puesto que ambos valores son mínimos, por lo que afirmamos que este modelo es capaz de diferenciar sin problema las diez enfermedades que se encuentran en nuestro dataset en función de la crítica que escribe el paciente.
Como conclusión para finalizar esta sección, podemos determinar que de todas las técnicas de clasificación probadas **Random Forest** es la que mejor resultados ha proporcionado entrenando un clasificador con una muy buena capacidad de predicción.
# Regresión para predecir la efectividad del fármaco por su puntuación.
El objetivo en esta primera sección consiste en intentar predecir la efectividad del medicamento en función de su puntuación, de 0 a 10, asignada por el paciente al que se le ha recetado. Para ello entrenaremos diversos modelos de regresión para comprobar cuál es el que mejor resultados puede proporcionar. Con el fin de llevar a cabo esta idea, necesitamos añadir una nueva columna de clasificación binaria a nuestro dataset convirtiendo las puntuaciones de los medicamentos, que se encuentran en un rango entre 0 y 10, a dos valores binarios que representen la efectividad del tratamiento. El intervalo [0,4] corresponderá al valor 0 y por lo tanto representará que para un paciente en particular el medicamento no ha sido de ayuda, mientras que el intervalo [5-10] se corresponderá con el valor 1 que representará la eficacia del tratamiento.
## Regresión lineal simple.
En esta sección entrenaremos el modelo más sencillo posible aplicando, para ello, la **regresión lineal simple**. La variable dependiente será el campo ratingBinaryLabel, el cual contiene las etiquetas de clasificación de los fármacos explicadas anteriormente, mientras que la variable predictora será la categoría que contiene las puntuaciones originales de los tratamientos: *rating*.
```{r, tidy=TRUE}
# Recorremos las filas del conjunto de entrenamiento y comprobamos los valores en los intervalos definidos.
for (i in 1:length(mini_training_dataset$rating)){
if (mini_training_dataset$rating[i] < 5)
mini_training_dataset$ratingLabel[i]<-0
else
mini_training_dataset$ratingLabel[i]<-1
}
# Realizamos el mismo procedimiento para el conjunto de test.
for (i in 1:length(mini_test_dataset$rating)){
if (mini_test_dataset$rating[i] < 5)
mini_test_dataset$ratingLabel[i]<-0
else
mini_test_dataset$ratingLabel[i]<-1
}
# Especificamos la variable a predecir, que en nuestro caso será el campo binario relacionado con la efectividad del tratamiento, y la variable predictora que se corresponde con el campo de las puntuaciones originales de los medicamentos.
modelo = mini_training_dataset$ratingLabel ~ mini_training_dataset$rating
# Entrenamos el modelo.
regresion_lineal = lm(modelo, mini_training_dataset)
# Comprobamos los resultados del modelo.
summary(regresion_lineal)
```
Tal y como podemos comprobar en el resumen estadístico mostrado en primer lugar, el modelo predictivo es confiable en tanto en cuanto su p-value tanto en el origen como en la pendiente es menor que 0.05. Además, observando el valor del atributo *Multiple R^2* podemos determinar que el modelo es capaz de predecir hasta un 80% de valores del campo *ratingLabel* mediante el atributo *rating*. Para ello aplica la siguiente ecuación, que se obtiene del *intercepto* y de la pendiente calculados préviamente: `rating_binario = -0.0684667 + 0.1173970 * rating_original`. Su interpretación reside en que por cada incremento en una unidad de la variable *rating* se corresponde a 0.117 del valor de *ratingLabel*.
Sin embargo, para medir la bondad del modelo de una forma más precisa y tradicional, procedemos a definir la siguiente función que es capaz de calcular las predicciones realizadas por el modelo entrenado préviamente, tanto para el conjunto de entrenamiento como para el de test, para posteriormente calcular ambas **tasas de error**. Encapsulamos el código referente a este procedimiento en una función puesto que así podremos reutilizarlo más adelante en los sucesivos modelos de regresión que aplicaremos a nuestro dataset.
```{r, tidy=TRUE}
# Función que recibe el modelo de regresión entrenado para realizar las predicciones tanto del conjunto de prueba como del conjunto de entrenamiento y calcula ambas tasas de error.
evaluar_modelo_regresion<-function(modelo, datos_train, datos_test) {
# Calculamos la probabilidad de cada muestra de pertenecer a una clase u a otra.
train_probab<-predict(modelo, datos_train, type="response")
test_probab<-predict(modelo, datos_test, type="response")
# Inicializamos todas las etiquetas a 0.
train_predicc<-rep(0, length(train_probab))
test_predicc<-rep(0, length(test_probab))
# En función de las probabilidades calculadas anteriormente clasficaremos el resto de muestras pertenecientes a la clase cuya etiqueta binaria es 1. Para ello consideramos que una muestra pertenece a esta clase si su probabilidad es mayor o igual a 0.5.
train_predicc[train_probab >= .5] = 1
test_predicc[test_probab >= .5] = 1
# Mostramos la matriz de confusión que nos aportará más información acerca de los falsos positivos.
cat("\nMatriz de confusión.\n")
print(table(pred=test_predicc, real=datos_test$ratingLabel))
# Ahora calculamos la tasa de error en % para cada conjunto.
train_error = mean(train_predicc != datos_train$ratingLabel)
test_error = mean(test_predicc != datos_test$ratingLabel)
cat("\nTasa de error en entrenamiento:",train_error*100,"%")
cat("\nTasa de error en prueba:",test_error*100,"%")
}
evaluar_modelo_regresion(regresion_lineal, mini_training_dataset, mini_test_dataset)
```
Tal y como podemos comprobar, la **tasa de error sobre el conjunto de entrenamiento**, como era de esperar es bastante pequeña. Sin embargo, el hecho de que sea 0% no es favorable puesto que, por el contrario, **el error en el conjunto de prueba** es considerablemente alto, por lo tanto podemos reflexionar acerca de si el modelo se ha ajustado demasiado a los datos de entrenamiento y por ello su capacidad de generalización es menor.
Asimismo, si analizamos la matriz de confusión, podemos comprobar que existe una alta tasa de errores al clasificar muestras en la categoría 1 mientras que en realidad pertenecen a la clase cuya etiqueta es 0, y viceversa. Con el objetivo de evaluar la calidad del modelo entrenado procedemos a representar la **curva ROC** así como estimar el área existente debajo de la misma.
```{r message=FALSE, tidy=TRUE}
# Función que realiza las predicciones con la función del paquete asociado a la curva ROC para posteriormente pintarla.
library("ROCR")
curvaROC<-function(modelo, etiquetas) {
# Obtenemos las predicciones.
predicciones<-prediction(modelo, etiquetas)
# Calculamos el rendimiento del modelo teniendo en cuenta los falsos positivos y los aciertos.
curva<-performance(predicciones, "tpr", "fpr")
# Dibujamos la curva.
plot(curva, col="green", add=FALSE, main="Curva ROC. Regresión Lineal.", lwd = 2)
segments(0, 0, 1, 1, col='black')
grid()
# Calculamos el área debajo de la curva
curva.area = performance(predicciones, "auc")
cat("\nEl área bajo la curva ROC es", curva.area@y.values[[1]]*100,"%\n")
}
# Curva ROC para el conjunto de prueba
test_probab = predict(regresion_lineal, mini_test_dataset, type=c("response"))
curvaROC(test_probab, mini_test_dataset$ratingLabel)
```
Como podemos comprobar la curva ROC es prácticamente lineal y por ende el área bajo ella es bastante escasa. Este aspecto nos indica que la capacidad de generalización del modelo entrenado es bastante escasa, con lo que explica la tasa de error tan elevada sobre el conjunto de prueba que hemos obtenido préviamente.
## Regresión logística simple.
Aplicaremos esta técnica con el objetivo de entrenar un modelo predictivo capaz de averiguar, para cada muestra, la clase binaria a la que pertenece el tratamiento: 0 (no efectivo) o 1 (efectivo). Para ello se realizará un procedimiento similar al anterior solo que en este caso utilizaremos la función correspondiente a la **regresión logística** para llevar a cabo el entrenamiento del sistema.
```{r message=FALSE, warning=FALSE, tidy=TRUE}
# Entrenamos un modelo predictivo con regresión logística. Para ello especificamos como variable dependiente la que deseamos predecir, que en nuestro caso será ratingLabel, y como variable predictora la puntuación original en el intervalo [0, 10].
regresion_logistica = glm(ratingLabel~rating, family=binomial(logit), data=mini_training_dataset)
# Resumen acerca del modelo entrenado.
summary(regresion_logistica)
# Evaluación del modelo entrenado.
evaluar_modelo_regresion(regresion_logistica, mini_training_dataset, mini_test_dataset)
```
Tal y como podemos comprobar el modelo entrenado con la técnica actual no es confiable puesto que ambos campos tienen un p-value muy cercano a 1, por lo que podemos concluir que no puede ser predicha la etiqueta binaria mediante las puntuaciones originales de forma lineal.
## Regresión polinomial
Si bien los resultados de los modelos predictivos entrenados mediante técnicas lineales no han sido buenos, vamos a aplicar una técnica no lineal con el objetivo de comprobar si la varible binaria *ratingLabel* puede ser predicha mediante modelo polinómico. Para aplicar esta técnica es indispensable incluir algunos componentes no lineales, como pueden ser nuevos predictores obtenidos a partir de aplicar técnicas sobre los originales como elevarlos a una potencia. Comenzaremos utilizando un polinomio de grado 2, para lo que necesitaremos incluir la variable *rating*, con la que intentamos predecir la clase a la que pertenece cada tratamiento, elevada al cuadrado de la siguiente forma.
```{r, tidy=TRUE}
# Entrenamos el modelo con regresión polinomial estableciendo un polinomio de grado 2.
regresion_polinomica<-lm(formula=ratingLabel~poly(rating, 2), data=mini_training_dataset)