-
Notifications
You must be signed in to change notification settings - Fork 4
/
manual-es.Rmd
1184 lines (813 loc) · 66.8 KB
/
manual-es.Rmd
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
---
title: "Analizando datos de Open Contracting"
author: "Rodrigo Parra"
subtitle: Un Manual usando el lenguaje de programación R
lang: es-ES
output:
bookdown::html_document2:
fig_caption: yes
theme: cosmo
toc: yes
bookdown::pdf_document2:
fig_caption: yes
toc: yes
bookdown::word_document2:
fig_caption: yes
toc: yes
urlcolor: blue
---
<style>
body {
text-align: justify
}
.caption {
text-align: center
}
caption {
text-align: center
}
</style>
\newpage
# Introducción
El objetivo de este manual es proveer la base necesaria para analizar y visualizar datos de Open Contracting usando el lenguaje de programación R a las personas interesadas en esta interesante y desafiante tarea. Un conocimiento básico de programación de computadoras será útil al leer este documento; sin embargo, se proveerá una guía suficiente para que lectores menos técnicos puedan también seguir el manual.
Como parte de esta guía, se obtendrán, limpiarán, analizarán y graficarán datos de Open Contracting de 4 países miembros de la Open Contracting Partnership: Paraguay, México, Uruguay y Colombia. Este documento fue escrito utilizando [R Notebooks](https://rmarkdown.rstudio.com/r_notebooks), una herramienta que permite integrar Markdown y código R de manera conveniente, pudiendo exportarse el resultado en formato HTML y PDF. El código fuente de este proyecto está disponible [aquí](https://https://github.com/rparrapy/ocds-r-manual).
El resto de este documento está organizado de la siguiente manera:
* El resto de esta sección introduce el estándar de datos Open Contracting, describiendo brevemente sus principales elementos, y el lenguaje de programación R, cubriendo sus funcionalidades más importantes y como instalarlo.
* La sección 2 trata de la adquisición y limpieza de datos, haciendo énfasis en la lectura y procesamiento de archivos en formato JSON con R.
* La sección 3 presenta el [Tidyverse](https://www.tidyverse.org), una colección de paquetes de R diseñados para realizar tareas de ciencia de datos.
* Finalmente, la sección 4 introduce [ggplot2](https://ggplot2.tidyverse.org) y la gramática de gráficos en la cual está basado, para luego dibujar varias visualizaciones que describen los datasets adquiridos.
## Una introducción al estándar de datos Open Contracting
Al requerir que los datos se compartan de forma estructurada, reusable y legible por las máquinas, los datos abiertos abren nuevas oportunidades para la participación y el empoderamiento ciudadanos. El [estándar de datos Open Contracting](https://www.open-contracting.org/data-standard/) fue creado para aplicar estos principios al ciclo de vida de contrataciones públicas incluyendo la planificación, licitación, adjudicación, contratación y ejecución.
Este estándar de datos, diseñado y desarrollado a través de un proceso abierto, permite a los gobiernos alrededor del mundo compartir sus datos de contrataciones, mejorando así los niveles de transparencia en las compras públicas, y haciendo posible el análisis de los sistemas de contrataciones públicas en términos de eficiencia, efectividad, justicia e integridad. Además, el equipo de help desk está disponible para asistir a potenciales usuarios en el proceso de adopción del estándar.
La intención de esta sección es introducir el estándar al lector, los casos de uso para los cuales fue diseñado y los conceptos básicos necesarios para su aplicación. La mayoría del contenido fue obtenido de la documentación oficial del estándar; para una introducción más detallada del estándar, favor referirse a la [guía para empezar](http://standard.open-contracting.org/latest/en/getting_started/).
### Usuarios y casos de uso
El estándar fue diseñado con cuatro grupos de necesidades de usuario en mente:
* Lograr un buen valor por dinero para el gobierno
* Fortalecimiento de la transparencia, rendición de cuentas e integridad de los contratos públicos
* Permitir al sector privado una competencia justa por contratos públicos
* Control de la eficacia de la prestación de servicios
Para saber quien está usando datos publicados de acuerdo al estándar y de que manera lo están haciendo, dirigirse al [sitio web de la Open Contracting Partnership](http://www.open-contracting.org/). Cuatro casos de uso potenciales de datos abiertos de contrataciones son:
* Valor por dinero en la adquisición: ayudando a oficiales de gobierno a lograr un buen valor por dinero durante el proceso de contratación, y analizando si esta meta fue lograda con posterioridad.
* Detección de fraude y corrupción: identificando banderas rojas que puedan indicar corrupción mediante el estudio de contratos individuales o redes de contratación basadas en financiación, propiedad e intereses.
* Competencia por contratos públicos: permitiendo a empresas privadas comprender el potencial de los canales de oportunidades de compras públicas, mediante el estudio de información relacionada a contrataciones pasadas y presentes.
* Supervisión de la prestación de servicios: ayudando a actores interesados a aprovechar la trazabilidad del proceso de contrataciones públicas con fines de monitoreo, relacionando datos de presupuesto a los contratos y resultados de su ejecución.
### El proceso de contratación
El estándar define el proceso de contratación como:
> Toda la información relativa a la planeación, la licitación, las adjudicaciones, los contratos y la ejecución de contratos relacionados con un solo proceso de iniciación.
El estándar cubre todas las etapas del proceso de contratación, aunque algunos procesos pueden no incluir todos los pasos posibles. Las etapas del proceso de contratación, con ejemplos de atributos que pueden estar asociados a cada una, se presentan en la figura 1.
```{r include=FALSE}
knitr::opts_chunk$set(cache = TRUE, warning = FALSE,
message = FALSE, cache.lazy = FALSE)
```
```{r, echo=FALSE, fig.cap="Etapas del proceso de contratacion."}
knitr::include_graphics("procurement-stages-es.png")
```
Con fines de identificación, cada proceso de contratación tiene asignado un identificador único de Open Contracting (ocid), el cual puede utilizarse para relacionar datos de diferentes etapas. De modo a evitar solapamiento entre publicadores, un publicador puede incluir un prefijo a todos los identificadores que genera. Se recomienda a los publicadores registrar su prefijo [aquí](http://standard.open-contracting.org/latest/en/implementation/registration/).
### Documentos
Los procesos de contratación son representados como **documentos** en el estándar de datos Open Contracting (OCDS de aquí en adelante, por brevedad). Cada documento está compuesto por varias **secciones**, mencionadas a continuación:
* metadatos de la entrega (release metadata): información contextual sobre cada entrega de datos;
* partes (parties): información sobre las organizaciones y otros participantes involucrados en el proceso de contratación;
* planeación (planning): información sobre los objetivos, presupuestos y proyectos a los que se refiere un proceso de contratación;
* licitación (tender): información sobre la forma en que tendrá lugar la licitación o la forma en que se ha realizado;
* adjudicación (award): información sobre las adjudicaciones otorgadas como parte de un proceso de contratación;
* contracto (contract): información sobre contratos firmados como parte de un proceso de contratación;
* implementación (implementation): información sobre el progreso de cada contrato hasta su finalización.
Un documento JSON de ejemplo con este estructura luce de la siguiente manera:
```json
{
"language": "en",
"ocid": "contracting-process-identifier",
"id": "release-id",
"date": "ISO-date",
"tag": ["tag-from-codelist"],
"initiationType": "tender",
"parties": {},
"buyer": {},
"planning": {},
"tender": {},
"awards": [ {} ],
"contracts":[ {
"implementation":{}
}]
}
```
Existen dos tipos de documentos definidos en el estándar:
* **Entregas** son inmutables y representan actualizaciones al proceso de contratación. Por ejemplo, pueden utilizarse para notificar a usuarios acerca de nuevas licitaciones, adjudicaciones, contratos y otras actualizaciones. De este modo, un único proceso de contratación puede tener muchas entregas.
* **Registros** son instantáneas del estado actual del proceso de contratación. Un registro debe ser actualizado cada vez que se publica una entrega asociada al proceso de contratación; de este modo, debe existir un único registro por cada proceso de contratación.
### Campos
Cada sección puede contener varios **campos** especificados en el estándar, los cuales se utilizan para representar datos. Estos elementos pueden aparecer varias veces en diferentes secciones del mismo documento; por ejemplo, pueden haber items en la licitación (para indicar items que un comprador desea comprar), en la adjudicación (para indicar items que han sido adjudicados) y en el contrato (para indicar items que forman parte del contrato). Algunos campos, acompañados de documentos JSON correspondientes, se presentan a continuación.
#### Partes (Organizaciones)
```json
{
"address": {
"countryName": "United Kingdom",
"locality": "London",
"postalCode": "N11 1NP",
"region": "London",
"streetAddress": "4, North London Business Park, Oakleigh Rd S"
},
"contactPoint": {
"email": "[email protected]",
"faxNumber": "01234 345 345",
"name": "Procurement Team",
"telephone": "01234 345 346",
"url": "http://example.com/contact/"
},
"id": "GB-LAC-E09000003",
"identifier": {
"id": "E09000003",
"legalName": "London Borough of Barnet",
"scheme": "GB-LAC",
"uri": "http://www.barnet.gov.uk/"
},
"name": "London Borough of Barnet",
"roles": [ ... ]
}
```
#### Montos
```json
{
"amount": 11000000,
"currency": "GBP"
}
```
#### Artículos
```json
{
"additionalClassifications": [
{
"description": "Cycle path construction work",
"id": "45233162-2",
"scheme": "CPV",
"uri": "http://cpv.data.ac.uk/code-45233162.html"
}
],
"classification": {
"description": "Construction work for highways",
"id": "45233130",
"scheme": "CPV",
"uri": "http://cpv.data.ac.uk/code-45233130"
},
"description": "string",
"id": "0001",
"quantity": 8,
"unit": {
"name": "Miles",
"value": {
"amount": 137000,
"currency": "GBP"
}
}
}
```
#### Periodos de tiempo
```json
{
"endDate": "2011-08-01T23:59:00Z",
"startDate": "2010-07-01T00:00:00Z"
}
```
#### Documentos
```json
{
"datePublished": "2010-05-10T10:30:00Z",
"description": "Award of contract to build new cycle lanes to AnyCorp Ltd.",
"documentType": "notice",
"format": "text/html",
"id": "0007",
"language": "en",
"title": "Award notice",
"url": "http://example.com/tender-notices/ocds-213czf-000-00001-04.html"
}
```
#### Hitos
```json
{
"description": "A consultation period is open for citizen input.",
"dueDate": "2015-04-15T17:00:00Z",
"id": "0001",
"title": "Consultation Period"
}
```
### Extensiones y listas de códigos
Además de los campos comunes, el esquema OCDS define algunos campos que pueden usarse solamente en secciones específicas, por ejemplo *titulos* y *descripciones* de licitaciones, adjudicaciones y contratos. En algunos casos, los publicadores pueden requerir campos que no se proporcionan en el esquema central de OCDS; una **extensión** permite definir nuevos campos que pueden usarse en estos casos. Una lista de extensiones disponibles se encuentra publicada [aquí](http://standard.open-contracting.org/latest/en/extensions); en caso de que no exista una extensión que se ajuste a las necesidades del publicador, se anima al mismo a colaborar en la creación de una nueva extensión comunitaria.
Otro concepto que vale la pena mencionar son las listas de códigos. Las listas de códigos son colecciones de cadenas con distinción entre mayúsculas y minúsculas con etiquetas asociadas, disponibles en cada idioma al que se ha traducido OCDS. Los publicadores deben utilizar valores de las listas de códigos cuando sea posible para mapear sus sistemas de clasificación actual; en caso de ser necesario, los campos de detalle pueden utilizarse para proveer información de clasificación más detallada. Existen dos tipos de listas de códigos:
* **Listas de códigos cerradas** son conjuntos fijos de valores. Si un campo se asocia a una lista de códigos cerrada, debe aceptar como valor únicamente una de las opciones de esta lista.
* **Listas de códigos abiertas** son conjuntos de valores recomendados. Si un campo se asocia a una lista de códigos abierta, debe aceptar valores de la lista pero puede aceptar también otros valores.
El estándar de datos Open Contracting se mantiene usando [JSON Schema](http://json-schema.org). En esta sección introdujimos y describimos sus secciones principales y elementos comunes del esquema, proveyendo documentos JSON como ejemplos de estos bloques básicos. Para acceder a una referencia del esquema JSON completo, el lector puede dirigirse a la [documentación oficial](http://standard.open-contracting.org/latest/en/schema/).
## Una introducción al lenguaje de programación R
R es un lenguaje de programación interpretado multiparadigma y un ambiente de desarrollo software orientado a la computación estadística, utilizado comúnmente para análisis de datos. Se publica bajo [la licencia GPL v2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html) y su mantenimiento está a cargo de la [R Foundation](https://www.r-project.org/foundation/), con paquetes binarios disponibles para GNU/Linux, macOS y Windows. Aunque el instalador básico incluye una interfaz por línea de comandos, varios ambientes de desarrollo integrados gráficos (IDE) se encuentran disponibles, entre los cuales RStudio y RStudio Server merecen mención especial[^1].
En esta sección introduciremos la sintaxis y algunas funcionalidades básicas de R; luego de esto, el lector estará mejor preparado para entender el código utilizado para el análisis del resto de esta guía. Una referencia completa del lenguaje R se encuentra fuera del alcance de este documento, razón por la cual varias funcionalidades se omiten en esta sección. Si el lector se sintiese inclinado a aprender más sobre R, por necesidad o curiosidad, una lista de manuales mantenidos por el equipo de desarrollo de R se encuentra disponible [aquí](https://cran.r-project.org/manuals.html).
Con propósitos de completitud y reproducibilidad, incluimos a continuación un fragmento de información de sistema.
```{r}
R.version
```
[^1]: El autor recomienda RStudio como la manera más conveniente de ejecutar código R y cree que este IDE es una de las principales razones tras la popularidad de R. En consecuencia, esta guía fue escrita usando RStudio v1.1.453 en macOS High Sierra.
### Operadores
Los operadores aritméticos y lógicos de R no deberían ser nada nuevo para la mayoría de los programadores, ya que son parecidos a los de otros lenguajes de programación. Merece mención el hecho de que los operadores aritméticos funcionan con escalares y colecciones.
| Operador | Descripción |
|:--------:|:-----------|
| **+** | Adición |
| **-** | Substracción |
| **\* ** | Multiplicación|
| **/** | División |
| **\*\* ** or **^**|Exponenciación |
| **%%** | Módulo |
Table: Algunos operadores aritméticos de R
| Operador | Descripción |
|:--------:|:-----------|
| **>** | Mayor que |
| **>=** | Mayor o igual que|
| **<** | Menor que|
| **<=** | Menor o igual que|
| **==** | Igual a |
| **!=** | Diferente a |
Table: Algunos operadores lógicos de R
### Variables
R soporta varios tipos de datos incluyendo escalares, vectores (de números, cadenas, booleanos, etc), matrices, dataframes y tibbles, entre otros. El operador **<-** se usa para asignar un valor a una variable. Algunos ejemplos de variables de estos tipos se muestran a continuación:
```{r}
a_scalar <- 4
a_number_vector <- c(1, 2, 3, 4, 5) # todos los elementos de un vector deben ser del mismo tipo
a_string_vector <- c("a1", "b2", "c3", "d4")
a_boolean_vector <- c(TRUE, FALSE)
# las listas pueden tener elementos de distintos tipos, asociados a una clave
a_list <- list(name = "John", last_name = "Deer", age = 42, married = FALSE)
# hay varias formas de acceder a un elemento de una colección
a_number_vector[0]
a_string_vector[[1]]
a_list$name
# una matriz es un tipo especial de vector, con número de filas y columnas como atributos
m <- matrix(c(1, 2, 3, 4), nrow = 2, ncol = 2)
m
```
### Data Frames y Tibbles
Los tipos de datos para almacenar tablas son centrales al propósito y funcionalidad de R, por este motivo merecen su propia subsección. El tipo más común de tabla en R son los data frames, los cuales pueden considerarse como una lista de vectores de igual longitud.
```{r}
name <- c("Mike", "Lucy", "John")
age <- c(20, 25, 30)
student <- c(TRUE, FALSE, TRUE)
df <- data.frame(name, age, student)
df
```
Los operadores de acceso presentados para otros tipos de datos también pueden usarse para obtener celdas de un data frame.
```{r}
df[1, ] # En R, las colecciones empiezan con el índice 1.
df[1, ]$name # Los valores de una fila puede accederse usando el nombre de la columna.
df$name # También es posible acceder a una columna directamente a partir del data frame.
```
Otro tipo de tabla de datos disponible son los Tibbles. Los Tibbles pertenecen al Tidyverse, un conjunto de librerías para realizar análisis de datos en R siguiendo buenas prácticas que se describirá con más detalle en otra sección. Por ahora, basta pensar en los Tibbles como data frames con algunos problemas solucionados. Por ejemplo, al imprimir un data frame se despliegan todas las filas en pantalla, lo cual puede resultar problemático para datasets grandes; en cambio, al imprimir un Tibble se despliegan solamente las primeras 10 filas por defecto.
Un data frame puede convertirse a Tibble y viceversa; este último caso puede ser útil al lidiar con librerías antiguas que no estén adaptadas al Tidyverse. El autor aconseja el uso de tibbles y recomienda (como muchos miembros de la comunidad) aprovechar las funcionalidades del Tidyverse y respetar sus delineamientos tanto como sea posible.
```{r message=FALSE, warning=FALSE}
# Packrat debe haber instalado las dependencias del proyecto,
# caso contrario, puedes hacerlo manualmente con el siguiente comando
# install.packages("tidyverse")
library(tidyverse) # Nota: así se importa un paquete en R
```
```{r}
tb <- as_tibble(df)
class(as.data.frame(tb))
```
### Funciones
La sintaxis para definición de funciones en R es bastante similar a la de otros lenguajes de programación. Una función recibe cero, uno o múltiples argumentos y ejecuta el código incluido en su cuerpo:
> function ( arglist ) {body}
Debido a que R apunta a un nicho específico (computación estadística), el lenguaje ofrece un conjunto variado de funciones por defecto, además de muchas librerías disponibles para su instalación. Por este motivo, las llamadas a funciones tienden a ser mucho más frecuentes en R que la definición de funciones.
La mayor parte del ecosistema de R está enfocado en tratar (limpiar, visualizar, modelar) datos tabulares. Como ejemplo sencillo de las funcionalidades estadísticas básicas podemos usar la función *summary* para obtener estadísticas descriptivas de una tabla.
```{r}
summary(tb)
```
Leer datos que se encuentran en formato tabular también es muy simple, como se muestra en el siguiente ejemplo que lee un archivo CSV como un data frame. El archivo corresponde al dataset iris, un dataset de ejemplo común en la literatura de ciencia de datos disponible [en línea](https://archive.ics.uci.edu/ml/datasets/iris).
```{r}
iris <- as_tibble(read.table("iris.csv", header = TRUE, sep = ","))
iris
```
Un último operador que vale la pena mencionar es el pipe *%>%*. Este operador permite encadenar funciones en R, lo cual favorece la legibilidad y (podría decirse) elegancia cuando se requiere una secuencia de llamadas a función.
```{r}
# Por ahora, no es necesario entender que hace cada función en este fragmento de código
iris %>%
group_by(Name) %>%
summarize_if(is.numeric, mean) %>%
ungroup()
```
El operador pipe pasa el valor de su operando izquierdo como el primer argumento de su operando derecho. De esta forma, al usarlo podemos evitar la declaración de variables para almacenar resultados intermedios (que contaminan el espacio de nombres) o el anidamiento de llamadas (que pueden ser difíciles de leer con tantos paréntesis).
### Estilo
Como observación final, aunque no existe una guía de estilo oficial para el lenguaje de programación R, el autor recomienda seguir la [guía de estilo del Tidyverse](http://style.tidyverse.org/index.html)[^2](lo cual no debería ser una sorpresa para el lector atento). El paquete [styler](http://styler.r-lib.org) es un linter y formateador de código conveniente para mantener el código fuente de acuerdo a los delineamientos de la guía; existe un add-in para RStudio disponible.
[^2]: Este notebook fue escrito intentando respetar la guía de estilo del Tidyverse. Cualquier fragmento de código que no lo siga puede atribuirse a la falta de buen café en ese punto del proceso de redacción :)
## Instalando R y otras dependencias
Asumiendo una máquina corriendo Ubuntu 18.04 Bionic Beaver como sistema operativo, un usuario con acceso *sudo* puede instalar el lenguaje de programación R y RStudio siguiendo estos pasos:
1. Actualiza los paquetes del sistema para asegurar que las dependencias ya instaladas estén actualizadas.
```{bash, eval=FALSE}
sudo apt update
```
2. Instala el lenguaje de programación R.
```{bash, eval=FALSE}
sudo apt -y install r-base
```
3. RStudio para Ubuntu viene empaquetado como un paquete *.deb*. La forma más fácil de instalarlo es usando el comando *gdebi*, el cual no es encuentra instalado en el sistema por defecto, pero puede instalarse con el siguiente comando.
```{bash, eval=FALSE}
sudo apt install gdebi-core
```
4. Ve a la [página de descargas del sitio oficial de RStudio](https://www.rstudio.com/products/rstudio/download/#download) y descarga el archivo correspondiente a tu sistema operativo. En nuestro escenario actual, el archivo sería *RStudio 1.1.456 - Ubuntu 16.04+/Debian 9+ (64-bit)*.
5. Desde el directorio en el cual descargaste el paquete en el último paso, instala RStudio usando *gdebi*.
```{bash, eval=FALSE}
sudo gdebi rstudio-xenial-1.1.456-amd64.deb
```
6. RStudio debería estar disponible para su ejecución mediante el comando *rstudio* o clickeando su ícono en el menú de aplicaciones.
Además de RStudio Desktop, el cual puede instalarse siguiendo los pasos anteriores, RStudio también se encuentra disponible en una versión de acceso remoto. RStudio Server es una aplicación que puede instalarse en un servidor web, ofreciendo de esta manero las mismas funcionalidades que la versión de escritorio a través de un navegador web. Para saber más acerca de esta alternativa y como instalarla, favor referirse a la [documentación oficial](https://www.rstudio.com/products/rstudio/download-server/).
El proyecto a partir del cual se generó este documento usa [Packrat](https://rstudio.github.io/packrat/) para la gestión de librerías de las cuales depende. Esto asegura la portabilidad del software (al importar el proyecto, se instalará la versión apropiada para el sistema operativo de cada paquete) y la reproducibilidad del análisis. De este modo, asumiendo que Git está instalado en el sistema, puedes bajar y jugar con este notebook de forma fácil:
1. Clona el proyecto
```{bash, eval=FALSE}
git clone https://github.com/rparrapy/ocds-r-manual.git
```
2. Abre el proyecto con RStudio usando la opción del menú **File -> Open Project...**
También usaremos una base de datos Postgres para nuestro análisis, por lo cual debemos instalar Postgres en caso de que no se encuentre en nuestro sistema. Podemos hacer esto ejecutando:
```{bash, eval=FALSE}
sudo apt install postgresql postgresql-contrib
```
El último comando instala el motor de bases de datos Postgres y crea un usuario *postgres* a ser utilizado como cuenta por defecto para el acceso a bases de datos.
# Adquisición de datos
Esta sección se enfoca en la lectura de datos a partir de diversas fuentes, el cual representa el primer paso para el proceso de análisis. Se cargarán datos de los cuatro países que conciernen a este documento, usándolos como ejemplos de distintos métodos de adquisición de datos:
* Los datos de Uruguay se leerán usando su API web pública.
* Los datos de Paraguay se leerán de archivos JSON previamente descargados.
* Los datos de México y Colombia se leerán de columnas JSONB de una base de datos Postgres. Como en este caso se tratará con volúmenes más grandes de datos, se usará como ejemplo de como procesar big data con R
usando JSON streams y procesamiento paralelo.
## De una API REST
El dataset de open contracting de Uruguay se encuentra disponible a través de un endpoint RSS, el cual incluye las URL de las últimas entregas. Cabe mencionar que, como se disponibilizan solo las entregas más recientes, no es posible realizar mucho análisis a profundidad basado en este dataset. Aún así, es un ejemplo apropiado para mostrar las funcionalidades de cliente HTTP de R; empezamos obteniendo el feed RSS y creando un dataframe donde cada fila corresponde a una entrega.
```{r}
library(XML)
xml_url <- "http://www.comprasestatales.gub.uy/ocds/rss"
xml_file <- xmlParse(xml_url)
xml_top <- xmlRoot(xml_file)[[1]]
index_tb <- as_tibble(xmlToDataFrame(xmlElementsByTagName(xml_top, "item")))
index_tb
```
En el fragmento anterior, empezamos importando la librería *XML* de R debido a que las respuestas de la API de Uruguay se encuentran en este formato. El método *xmlParse* obtiene el contenido del feed RSS y lo parsea, retornando un array anidado que representa un árbol. Procedemos entonces a obtener la raíz de este árbol y buscar cada elemento con la etiqueta *item* usando el método *xmlElementsByTagName*. Convertimos el resultado a un dataframe y posteriormente a un tibble usando *xmlToDataFrame* y *as_tibble respectivamente.
Procedemos luego a obtener el documento JSON correspondiente a cada entrega, usando los valores de la columna **link** del tibble definido con anterioridad. Existen muchas librerías para manejar documentos JSON con R, cada una con sus ventajas y desventajas; debido a su énfasis en el rendimiento y consumición de APIs web, en esta guía usamos [jsonlite](https://cran.r-project.org/web/packages/jsonlite/index.html).
```{r message=FALSE, warning=FALSE}
library(jsonlite)
```
```{r, cache=TRUE}
# corremos fromJSON (que obtiene el contenido de la URL) por cada fila del data frame
releases <- apply(index_tb["link"], 1, function(r) {
return(as_tibble(fromJSON(r, flatten = TRUE)[["releases"]]))
})
releases[[1]]
```
La principal función del fragmento de código anterior es *fromJSON*, la cual es proveida por *jsonlite*.
La utilizamos para obtener los documentos JSON a partir de la url contenida en la columna *link* de *index_tb*. La bandera flatten indica que queremos que la función aplane lo más posible la estructura de los JSON anidados, dejándolos en un formato más apropiado para su representación tabular. Repetimos este proceso por cada file del tibble usando la función *apply*, la cual ejecuta la función que recibe como parámetro por cada file y retorna una lista como resultado.
En este punto tenemos una lista de tibbles, cada uno representando una entrega. El siguiente paso es unir estos tibbles, rellenando los valores faltantes (por ejemplo, de acuerdo a la etapa a la que corresponden, algunas entregas contienen información de licitación mientras otras no) con NA (la constante de R que simboliza 'no disponible'). Podemos lograr esto usando la función *bind_rows*.
```{r, cache=TRUE}
releases_tb <- bind_rows(releases)
```
Debido a que los datos de Uruguay incluyen solo las últimas 500 entregas, la compilación de estas entregas para formar registros no es posible (no hay suficientes datos). Por este motivo, este fue el último uso de los datos uruguayos en esta guía.
## A partir de archivos JSON
También podemos leer registros o entregas a partir de un conjunto de archivos JSON, publicados por uno o más publicadores oficiales asociados a un país. Este es el caso de Paraguay, el siguiente país que añadiremos a nuestro análisis. Los datos abiertos de contrataciones de este país son publicados por la Dirección Nacional de Contrataciones Públicas (DNCP) y por el Ministerio de Hacienda; como comparten ocids, es posible usar los datos publicados por ambos de forma conjunta.
Para continuar con esta guía es necesario obtener una copia de los datasets y almacenarla en el disco duro local, bajo el directorio *./data* (el cual se encuentra vacío luego de clonar el proyecto). Esto es:
1. Dirígete a [https://mega.nz/#F!PCQFzAyY!m54DS0hA3pyKXqergJUBFQ](https://mega.nz/#F!PCQFzAyY!m54DS0hA3pyKXqergJUBFQ).
2. Descarga el archvio *data.zip* y extrae su contenido en *./data*.
3. En caso de éxito, dos nuevas carpetas con archivos .json deben estar disponibles en el sistema: *./data/records_dncp* y *./data/releases_mh*.
Antes de leer los datos, tomémonos un tiempo para hacer que nuestro procesamiento de datos sea más eficiente. El tamaño de los datasets que analizamos en esta guía va desde no trivial (el procesamiento toma unos pocos minutos) hasta desafiante (el procesamiento toma varias horas). Esto se agrava si consideramos que R se ejecuta en un único hilo por defecto, desaprovechando los múltiples cores que ofrecen la mayoría de las computadoras modernas. Para cambiar esto, podemos usar la librería *parallel*, la cual permite ejecutar funciones como apply y similares de forma paralela.
```{r, warning=FALSE, message=FALSE}
library(glue) # Utilitario para concatenar cadenas
library(parallel)
library(lubridate) # si tienes que trabajar con fechas, hazte el favor de usar esta librería
# Calculamos el número de cores
no_cores <- detectCores() - 1
# Iniciamos el cluster
cl <- makeCluster(no_cores)
clusterExport(cl, c("fromJSON", "paste", "as_tibble", "ymd_hms", "select"))
```
Para empezar a usar parallel, definimos un cluster de tamaño igual al número de cores disponibles en nuestra máquina menos uno (si no dejamos ningún core libre para otro cómputo la computadora puede dejar de responder) con la función *makeCluster*. Cada uno de los trabajadores de nuestro cluster ejecuta un intérprete de R sin ninguna otra dependencia; para importar las librerías que utilizaremos usamos la función *clusterExport*.
Ahora estamos listos para empezar con los datos de la DNCP, los cuales están almacenados en *data/records_dncp*:
```{r, cache=TRUE}
files <- list.files("data/records_dncp/", "*.json")
records <- parLapply(cl, files, function(r) {
file <- paste("data/records_dncp/", r, sep = "")
parsed <- fromJSON(file, flatten = TRUE)
records <- parsed[["records"]]
publishedDate <- ymd_hms(parsed[['publishedDate']])
records$publishedDate <- rep(publishedDate, nrow(records))
return(as_tibble(records))
})
records[[1]]
```
El anterior fragmento de código usa varias de las funciones que ya utilizamos como *fromJSON*, *as_tibble* y otras. Hay tres nuevas funciones que mereces una explicación:
* *list.files* retorna una lista de direcciones correspondientes a archivos almacenados en la dirección que recibe como primer parámetro, que matchean la expresión regular que recibe como segundo parámetro.
* *paste* concatena todas las cadenas que recibe, colocando *sep* entre ellas.
* *parLapply* es la versión paralela de *lapply*, la cual ejecuta la función que recibe como parámetro en los diferentes trabajadores del cluster. Esto puede hacerse debido a que la lectura y parseo de archivos JSON es una tarea vergonzosamente paralela.
```{r, cache=TRUE}
dncp_records_tb <- bind_rows(records)
remove(records)
dncp_records_tb
```
Para liberar un poco de espacio, usamos la función *remove* que elimina explícitamente una variable de memoria. Los datos del Ministerio de Hacienda pueden leerse de forma similar:
```{r, cache=TRUE}
files <- list.files("data/releases_mh/", "*.json")
releases <- parLapply(cl, files, function(r) {
file <- paste("data/releases_mh/", r, sep = "")
parsed <- fromJSON(file, flatten = TRUE)
releases <- parsed[["releases"]]
publishedDate <- ymd_hms(parsed[['publishedDate']])
# hay archivos que no incluyen ninguna entrega
if (!is.null(nrow(releases)) && nrow(releases) > 0) {
releases$publishedDate <- rep(publishedDate, nrow(releases))
}
return(as_tibble(releases))
})
mh_releases_tb <- bind_rows(releases)
remove(releases)
mh_releases_tb
```
## Desde una base de datos Postgres con columnas JSONB
Para México y Colombia procesaremos los datos en modo streaming; esto es, un registro a la vez. Los dataframes y tibbles se almacenan por completo en memoria, en cambio el procesamiento de un stream requiere mantener solo un registro en memoria. Por esta razón, este paradigma de procesamiento de datos resulta útil al lidiar con big data.
Para continuar con esta guía, debes descargar los archivos de backup de estas bases de datos a tu computadora y restaurarlos en tu instancia local de Postgres. Para hacerlo:
1. Dirígete nuevamente a [https://mega.nz/#F!PCQFzAyY!m54DS0hA3pyKXqergJUBFQ](https://mega.nz/#F!PCQFzAyY!m54DS0hA3pyKXqergJUBFQ).
2. Descarga los archivos *ocds_colombia.dump* y *ocds_mexico.dump* a tu disco duro local.
3. Crea las bases de datos ejecutando *sudo -u postgres createdb ocds_colombia* y *sudo -u postgres createdb ocds_mexico* desde una terminal.
3. En otra sesión de terminal, desde el directorio donde descargaste los backups, ejecuta *pg_restore -d ocds_colombia ocds_colombia_dump* y *pg_restore -d ocds_mexico ocds_mexico.dump*.
4. Verifica las bases de datos que acabas de crear. Deben contener múltiples tablas que se crearon durante el proceso de restauración. En particular, la tabla *data* debe contener los registros para nuestro análisis.
La librería que utilizamos (jsonlite) incluye la función *stream_in* para soportar [ndjson](http://ndjson.org), un formato conveniente para almacenar múltiples documentos JSON en un único archivo. Como nuestros registros están almacenados en una base de datos Postgres, debemos escribir una funcionalidad similar nosotros mismos. Hagamos exactamente esto en los próximos fragmentos de código:
```{r}
library(RPostgreSQL)
db_engine <- "PostgreSQL"
host <- "localhost"
user <- "postgres" # si te preocupa la seguridad
password <- "" # deberías cambiar estas 2 líneas
port <- 5433
query <- "SELECT id, data FROM data"
drv <- dbDriver(db_engine)
con_colombia <- dbConnect(drv, host = host, port = port,
dbname = "ocds_colombia", user = user, password = password)
con_mexico <- dbConnect(drv, host = host, port = port,
dbname = "ocds_mexico", user = user, password = password)
```
Nos conectaremos y realizaremos consultas a Postgres a través de [DBI](https://cran.r-project.org/web/packages/DBI/index.html), una definición de interfaz para comunicaciones entre R y motores de bases de datos relacionales. En particular, usaremos [RPostgreSQL](https://cran.r-project.org/web/packages/RPostgreSQL/index.html), una implementación de DBI para bases de datos Postgres.
```{r}
stream_in_db <- function(con, query, page_size = 1000, acc = 0) {
current_id <- 0
return(function(handler) {
repeat{
paged_query <- paste(query, "WHERE id > ", current_id, "ORDER BY id ASC LIMIT", page_size)
data <- dbGetQuery(con, paged_query)
if (dim(data)[1] == 0) {
break
}
acc <- handler(data[['data']], acc)
current_id <- tail(data[['id']], n=1)
}
return(acc)
})
}
stream_in_colombia <- stream_in_db(con_colombia, query)
stream_in_mexico <- stream_in_db(con_mexico, query)
```
Repasemos lo que acabamos de hacer:
1. Primero, definimos una función *stream_in_db* que recibe los parámetros de conexión a la base de datos y una consulta, y ejecuta esta consulta contra la base de datos especificada.
1. *stream_in_db* retorna una función, la cual aplica un handler que recibe como parámetro a cada página de resultados.
1. Finalmente, usamos *stream_in_db* para obtener funcionaes apuntando a las bases de datos de México y Colombia.
El desempeño de la paginación de resultados usando *LIMIT* and *OFFSET* se degrada conforme visitamos páginas más alejadas del inicio. Para acelarar el procesamiento lo más posible usamos una técnica de paginación conocida como *keyset pagination*, la cual aprovecha el hecho de que existe un índice definido para la clave primaria. Para una comparación más detallada de métodos de paginación se recomienda leer [este artículo](http://allyouneedisbackend.com/blog/2017/09/24/the-sql-i-love-part-1-scanning-large-table/).
Para probar nuestras funciones de streaming, podemos definir un handler de prueba que solo cuente el número de filas que resultan de nuestra consulta:
```{r message=FALSE, warning=FALSE, eval=FALSE}
sanity_checker <- function(data, acc) {
m <- parLapply(cl, data, function(e) {
t <- fromJSON(e, flatten = TRUE)
return(1)
})
return(acc + Reduce("+", m))
}
```
# Análisis de datos de contrataciones
Esta sección introduce un conjunto de herramientas útiles para realizar análisis de datos usando R, acompañados de algunos ejemplos básicos para demostrar sus funcionalidades.
## Introducción al Tidyverse
El Tidyverse es una colección de paquetes R orientados a las tareas de ciencia de datos; además de las librerías, comparte un conjunto de estructura de datos, una guía de estilo y una filosofía subyacente para el análisis de datos. Los principales [paquetes](https://www.tidyverse.org/packages/) del Tidyverse son:
* gglplot2: un sistema para crear gráficos de forma declarativa, basado en la "Gramática de los Gráficos", una herramienta que permite al usuario describir la mayoría de los mapeos utilizados en la visualización de datos de forma concisa. Uno provee los datos, le indica a ggplot2 como mapear las variables a *aesthetics*, que primitivas visuales utilizar, y la herramienta se encarga de los detalles.
* dplyr: una implementación de una gramática para la manipulación de datos, la cual provee un conjunto consistente de verbos para solucionar la mayoría de los desafíos de la manipulación de datos.
* tidyr: provee un conjunto de funciones que ayudan en la obtención de datos ordenados (tidy). Los datos ordenados son datos en una forma consistente: cada variable corresponde a exactamente una columna, y cada columna es exactamente una variable.
* readr: provee una forma rápida y amigable de leer datos tabulares (como csv, tsv y fwf).
* purrr: mejora las funcionalidades de R para la programación funcional proveyendo un conjunto de herramientas completo y consistente para trabajar con funciones y vectores.
* tibble: una reimplementación de la estructura de datos estelar de R: el dataframe. El usuario debería estar familiarizado con esta estructura de datos en este punto de la guía.
* stringr: provee un conjunto cohesivo de funciones diseñadas para facilitar el trabajo con cadenas.
* forcats: provee una suite de herramientas para solucionar problemas comunes con factores, el tipo de datos utilizado en R para representar variables categóricas.
Los tres primeros paquetes son los más importantes para los propósitos de esta guía. Por este motivo, tidyr y dplyr se cubrirán en el resto de esta sección y ggplot2 se describirá en la siguiente.
## Funciones para limpieza de datos
De acuerdo a la filosofía del Tidyverse, la limpieza de datos es el proceso de hacer que los datos sean ordenados. En un conjunto de datos ordenado:
* Cada variable debe tener su propia columna.
* Cada observación debe tener su propia fila.
* Cada valor debe tener su propia celda.
Todos los paquetes del Tidyverse están diseñados para trabajar con datos ordenados; por tanto, al lidiar con un dataset desordenado, el primer paso de nuestro análisis deber ser usar **tidyr** para limpiarlo. Existen tres verbos principales proveidos por este paquete para ayudarnos a limpiar los datos: gather, spread y separate.
### Recoger
Un inconveniente frecuente en los datos desordenados es un dataset en el cual los nombres de las columnas no son *nombres* de variables sino *valores*. A modo de ejemplo, considera el siguiente fragmento:
```{r}
world_population = tibble(
country = c("Paraguay", "Uruguay", "Colombia", "Mexico"),
"2017" = c(7000000, 3000000, 45000000, 127000000),
"2018" = c(7200000, 3200000, 46000000, 128000000),
)
world_population
```
En el ejemplo anterior, los datos están desordenados porque *2017* y *2018* son valores de la variable implícita *año*. Esto implica que cada fila corresponde a dos observaciones, no a una. Podemos solucionar este problema aplicando la función *gather* como sigue:
```{r}
world_population %>% gather(`2017`, `2018`, key = "year", value = "inhabitants")
```
*Gather* recibe como parámetros los nombres de las columnas que queremos pivotar, y los nombres de las dos columnas nuevas que queremos crear. ¡Mucho mejor! En el resultado final, las columnas problemáticas fueron eliminadas y nuestro dataset es ahora 100% ordenado.
### Esparcir
Gather es útil cuando una fila corresponde a más de una observación. **Spread** funciona en el caso opuesto, cuando una única observación corresponde a múltiples filas. Considera la siguiente extensión de nuestro dataset de ejemplo:
```{r}
world_count = tibble(
country = c("Paraguay", "Uruguay", "Colombia", "Mexico", "Paraguay", "Uruguay", "Colombia", "Mexico"),
year = 2018,
type = c("inhabitants", "inhabitants", "inhabitants", "inhabitants", "cars", "cars", "cars", "cars"),
count = c(7000000, 3000000, 45000000, 127000000, 1000000, 500000, 10000000, 75000000)
)
world_count
```
En este caso tenemos variables almacenadas como valores de celdas, este caso se con *inhabitants* y *cars*. ¿Cómo lo arreglamos? Basta con dejar que **spread** haga su magia:
```{r}
world_count %>% spread(key = type, value = count)
```
*Spread* convierte cada valor en la columna dada por su parámetro *key* a una columna separada, completando los valores de la misma a partir de la columna dada por su parámetro *value*.
### Separar
La última función de tidyr que visitaremos, **separate**, permite solucionar casos en los cuales valores múltiples están almacenados en la misma celda. Por ejemplo, considera el resultado de parsear el feed RSS de Uruguay que descargamos en la sección anterior.
```{r}
index_tb
```
Está claro que la columna title contiene valores para dos variables: *id_compra* y *release_id*, los cuales están separados por una coma. Usemos **separate** para ordernar las cosas:
```{r}
index_tb %>%
separate(title, into = c("id_compra", "release_id"), sep = ",") %>%
transform(id_compra=str_replace(id_compra,"id_compra:","")) %>%
transform(release_id=str_replace(release_id,"release_id:","")) %>%
# las últimas 2 líneas solo eliminan los prefijos innecesarios
head(5)
```
*Separate* separa el valor de una columna usando *sep* como separador; los nombres de las nuevas columnas están dadas por el parámetro *into*, el cual debe ser una colección de cadenas. Las últimas dos líneas son un vistazo previo del paquete que cubriremos a continuación: dplyr.
## Funciones para el análisis de datos
Una vez que limpiamos nuestros datos con **tidyr**, los siguientes pasos del análisis de datos involucran la manipulación de los datos de distintas maneras. Seleccionar filas y columnas de acuerdo a ciertas condiciones, añadir columnas compuestas, sumarizar datos, etc. pueden citarse como ejemplos de las operaciones que se realizan frecuentemente como parte del proceso analítico. El Tidyverse incluye **dplyr**
como la herramienta a utilizar para estas tareas, a continuación cubriremos algunas de sus funciones básicas.
### Mutar
La función **mutate** permite al usuario añadir una nueva columna a un tibble basado en los valores de una (o más) columnas ya existentes. Usemos el dataset de entregas de Uruguay para demostrar un caso en la cual está función puede ser de utilidad:
```{r}
releases_tb
```
Supongamos que necesitamos una columna con el *nombre* del mes en el cual se publicó una entrega. Este valor no está directamente disponible en el dataset, pero la información necesaria está contenida en la columna *date* y puede ser extraida con la ayuda de **mutate**:
```{r}
uruguay_releases_with_month = releases_tb %>% mutate(month = month.name[month(date)])
## month.name es un vector con el nombre de cada mes
uruguay_releases_with_month[c('ocid', 'month')]
```
*Mutate* recibe un tibble como primer parámetro; recibe una expresión de asignación de variable como segundo parámetro, la cual se evalúa fila por fila de modo a producir la nueva columna.
### Seleccionar
En el fragmento de código anterior seleccionamos ciertas columnas de nuestro tibble usando la notación de índices de un arreglo. **dplyr** provee una función para hacer lo mismo, **select**:
```{r}
select(uruguay_releases_with_month, ocid, month)
```
*Select* recibe un número variable de parámetros, el primero debe ser un tibble o data frame y el resto son los nombres de las columnas a incluir en la selección.
### Filtrar
Una operación muy común al lidar con datos tabulares es seleccionar un subconjunto de filas de interés para el análisis. Por ejemplo, asumamos que solo estamos interesados en entregas relacionadas a adjudicaciones. La etapa del proceso de contratación a la que corresponde una entrega puede determinarse a partir de la columna *tag*.
```{r}
releases_tb[1,]$tag
```
Sabiendo esto, es fácil obtener la colección de entregas que corrsponden a adjudicaciones usando **filter** como se muestra a continuación:
```{r}
awards_tb = releases_tb %>% filter(tag == 'award')
awards_tb
```
```{r}
awards_tb[10, ]$tag
```
*Filter* recibe un tibble como primer parámetro y una expresión booleana como el segundo, la expresión booleana se evalúa para cada fila y solo las filas que resultan en *TRUE* se incluyen en el resultado final.
### Sumarizar
La sumarización nos permite agregar variables basados en un agrupamiento predefinido. Para ilustrar este concepto, revisitemos el dataset de Iris:
```{r}
iris %>%
group_by(Name) %>%
summarize_if(is.numeric, mean)
```
En el fragmento anterior, empezamos agrupando el dataset por el nombre de especie y luego sumarizamos cada columna numérica usando la media como función de agregación.
# Visualización de datos
La visualización de datos puede definirse como el mapeo visual de datos usando atributos visuales como tamaño, forma y color para representar la variación de los valores de las variables en un dataset. Es una buena forma de comunicar información compleja, debido a que identificar patrones y hacer comparaciones es más fácil que con los datos en su formato original.
Esta sección introduce al lector a algunos conceptos básicos de visualización de datos, proveyendo algunos delineamientos básicos para la elección de una representación visual apropiada para un conjunto de datos. Aunque adquirir conocimiento teórico es definitivamente útil, las buenas noticias para el lector son que R tiende a guiar gentilmente hacia la elección correcta (si el usuario lo permite). La última frase será más clara una vez que cubramos **ggplot2**, la principal librería de visualización de R y un componente importante del Tidyverse.
Finalmente, pondremos a prueba lo aprendido graficando datos de OCDS usando **ggplot2**.
## Introducción a la visualización de datos
Cada vez que visualizamos datos, estamos codificando variables usando atributos visuales como tamaño, forma o color. Considera el ejemplo de una variable cuantitativa, la diferencia entre los valores asociados a cada observación pueden representarse de distintas maneras, como se muestra en la figura 2.
```{r, echo=FALSE, fig.cap="Posible mapeo de atributos para una variable cuantitativa."}
knitr::include_graphics("visual_cues_es.jpeg")
```
Como el lector habrá notado, muchos mapeos son posibles, sin embargo no todos ellos son igualmente apropiados. De hecho, los estadísticos William Cleveland and Robert McGill exploraron esta idea mediante experimentos con personas voluntarias buscando determinar que atributos visuales codificaban la información cuantitativa de forma más precisa. Sus resultados se resumen en la figura 3:
```{r, echo=FALSE, fig.cap="Atributos preatentivos ordenados de acuerdo a su idoneidad para codificar variables cuantitativas."}
knitr::include_graphics("ordered_visual_cues_es.jpg")
```
Aunque este orden de preferencia es una buena guía para variables cuantitativas, otros tipos de datos se mapean de forma diferente a los atributos visuales. Por ejemplo, mientras el color es una elección pobre para codificar una variable cuantitativa, funciona bien para codificar una variable categórica.
Diferentes combinaciones de elecciones de codificación resultan en diferentes diagramas. En este manual usaremos cuatro tipos de diagramas, los cuales se introducirán al utilizarse en la siguiente sección: el diagrama de barras, el diagrama de líneas, el diagrama de caja y el histograma. Existen muchos otros tipos de diagramas, y consideraciones adicionales que pueden tenerse en cuenta al visualizar datos; para una guía más detallada de estos temas se recomienda el curso de visualización de datos de Peter Aldhous, disponible [en línea](http://paldhous.github.io/ucb/2016/dataviz/week2.html) (las figuras de esta sección se tomaron de este curso).
## Introducción a ggplot2
**ggplot2** es el paquete del Tidyverse para visualización de datos. Está basado en la *gramática de los gráficos*, una gramática formal para describir declarativamente la mayoría de los diagramas más comunes utilizados al visualizar datos.
Los diagramas se describen en ggplot usando un conjunto conciso de elementos los cuales pueden combinarse de acuerdo a una estructura básica definida por la gramática de los gráficos. Una versión simplificada de la gramática puede leerse a continuación:
```{r, eval=FALSE}
ggplot(data = [DATA]) +
[GEOM_FUNCTION](mapping = aes([MAPPINGS]))
```
Puedes considerar el anterior fragmento como una plantilla para gráficos. Para hacer un diagrama, simplemente rellena los elementos entre corchetes con valores reales:
* [DATA] es bastante evidente, es la posición del dataset que queremos visualizar.
* [GEOM] debe ser reemplazado por cualquier función **geom** proveida por ggplot2. Las funciones geom son objetos geométricos usados para representar datos como puntos, líneas, etc.
* Finalmente, los [MAPPINGS] deben ser una colección de **aesthetics**, los cuales son básicamente especificaciones de mapeos visuales para las variables que deseamos visualizar.
Usemos esta plantilla para graficar el dataset de Iris con un gráfico de dispersión:
```{r}
ggplot(data = iris) + geom_point(mapping = aes(x = PetalLength, y = PetalWidth, color = Name))
```
Como el lector habrá notado, **ggplot2** favorece la convención frente a la configuración y cualquier diagrama que grafiquemos incluye muchas buenas prácticas por defecto. Por ejemplo, en el diagrama de dispersión anterior obtuvimos etiquetas en los ejes y una leyenda *por defecto* especificando solo los mapeos mediante aesthetics.
Existen componentes adicionales de la gramática de **ggplot2**, pero para esta breve introducción nuestra plantilla simplificada es suficiente. Para más información acerca de los elementos disponibles, además de una lista exhaustiva de *geoms* y *aesthetics* favor visitar el [sitio oficial](https://ggplot2.tidyverse.org) de la librería.
## Visualizando datos de Open Contracting
Ahora que conocemos lo básico de **ggplot2** podemos empezar a dibujar unos cuantos diagramas, o casi. Como nuestro mayor interés es comparar montos por año y comprador, debemos empezar por extraer todas las filas de nuestro dataset que contienen al menos un contrato. Logramos esto construyendo un índice booleano con un elemento por fila de nuestro dataset, indicando si la fila correspondiente pasa la condición o no.
```{r}
contract_indices <- parApply(cl, dncp_records_tb, 1, function(r) {
contracts <- r['compiledRelease.contracts'][[1]]
return(!is.null(contracts) && !is.null(dim(contracts)))
})
```
Una vez construido nuestro índice booleano, podemos usarlo para filtrar los registros en los cuales no estamos interesados, manteniendo solo aquellos de utilidad para nuestro análisis; conseguimos esto ejecutando `dncp_records_tb[contract_indices, ]` en el siguiente fragmento de código. Luego, procedemos a extraer los contratos y el nombre del comprador de los registros de interés.
```{r}
contracts <- parApply(cl, dncp_records_tb[contract_indices, ], 1, function(r) {
result <- r['compiledRelease.contracts'][[1]]
result['buyer.name'] <- r['compiledRelease.buyer.name']
result['publishedDate'] <- r['publishedDate']
return(result)
})
```
El objeto `contracts` definido arriba es una colección anidada de contratos. Para convertirlo en un único tibble llamamos a la función `bind_rows`. Liberamos la memoria que ya no usamos explícitamente retirando el objeto `contracts` de memoria.
```{r}
contracts_dncp <- bind_rows(contracts)
rm(contracts)
```
Disponemos actualmente de un dataset de contratos publicados por la DNCP listo para el análisis y visualización. Podemos seguir pasos similares para obtener uno a partir del dataset del Ministerio de Hacienda.
```{r}
contracts <- parApply(cl, mh_releases_tb, 1, function(r) {
result <- r['contracts'][[1]]
result['buyer.name'] <- r['buyer.name']
result['publishedDate'] <- r['publishedDate']
return(result)
})
```
```{r}
contracts_hacienda <- bind_rows(contracts)
rm(contracts)
```
Sabemos que existe un solapamiento significativo entre las publicaciones de la DNCP y el Ministerio de Haciendo, de modo que unir los datasets de forma ingenua llevará a la duplicación de contratos haciendo inválido nuestro análisis. Primeramente, asegurémonos de que el solapamiento existe, seleccionando los contratos que están presentes en ambos datasets. La función `subset` selecciona filas de un tibble basada en una condición; en este caso, seleccionamos la intersección de ambos datasets por lo cual un resultado no vacío confirmaría la existencia de duplicados.
```{r}
overlap <- subset(
contracts_dncp,
(dncpContractCode %in% contracts_hacienda[['dncpContractCode']]))['dncpContractCode']
dim(overlap)
```
Habiendo confirmado la presencia de contratos duplicados, necesitamos encontrar una estrategia más inteligente para fusionar los datasets: podemos mezclar ambos tibbles y agruparlos de acuerdo al *dncpContractCode* (lo cual debería agrupar a duplicados juntos), ordenar las filas dentro de cada grupo por *publishedDate* de forma ascendente, y finalmente seleccionar la última fila de cada grupo. Este paso de preprocesamiento, implementado en el siguiente fragmento de código, asegura que incluimos únicamente la versión más actualizada de cada contrato en nuestro análisis.
```{r}
contracts_paraguay <- bind_rows(contracts_dncp, contracts_hacienda) %>%
group_by(dncpContractCode) %>%
arrange(publishedDate) %>%
slice(n()) %>%
ungroup
contracts_paraguay
```
Seguido, está el problema de la moneda. Examinemos que monedas fueron utilizadas y cuantos contratos se firmaron con cada una.
```{r}
contracts_paraguay %>%
group_by(value.currency) %>%
summarise(count = n())
```
Los resultados presentados en la anterior tabla muestran que la amplia mayoría de los contratos fueron firmados con montos en Guaraníes (PYG), un hecho poco sorprendente considerando que estamos analizando contratos del Paraguay. Podríamos incluir los contratos en dólares americanos multiplicando el monto por una tasa de cambio determinada, pero por simplicidad consideraremos solo los contratos en moneda local.
Continuando con nuestro análisis, obtengamos un tibble de nuestro dataset de contratos agrupados por año y algunas sumarizaciones interesantes. Primero, extraemos el año del campo *dateSigned* usando la función `mutate`, luego filtramos las filas con valores inválidos (NA) y seleccionamos solo los contratos en PYG usando la función `filter`. Finalmente, agrupamos los contratos restantes por año usando la función `group_by` y obtenemos el conteo y la suma de montos por grupo usando la función `summarise`.
```{r}
by_year <- contracts_paraguay %>%
mutate(signed_year = year(dateSigned)) %>%
filter(!is.na(signed_year), !is.na(value.amount),
signed_year < 2019, signed_year > 2009, value.currency == 'PYG') %>%
group_by(signed_year) %>%
summarise(count = n(), amount = sum(value.amount))