-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdatatable-reshape.html
602 lines (598 loc) · 34.7 KB
/
datatable-reshape.html
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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>Efficient reshaping using data.tables</title>
<style type="text/css">
/**
* Prism.s theme ported from highlight.js's xcode style
*/
pre code {
padding: 1em;
}
.token.comment {
color: #007400;
}
.token.punctuation {
color: #999;
}
.token.tag,
.token.selector {
color: #aa0d91;
}
.token.boolean,
.token.number,
.token.constant,
.token.symbol {
color: #1c00cf;
}
.token.property,
.token.attr-name,
.token.string,
.token.char,
.token.builtin {
color: #c41a16;
}
.token.inserted {
background-color: #ccffd8;
}
.token.deleted {
background-color: #ffebe9;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #836c28;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #5c2699;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
</style>
<style type="text/css">
body {
font-family: sans-serif;
max-width: 800px;
margin: auto;
padding: 1em;
line-height: 1.5;
box-sizing: border-box;
}
body, .footnotes, code { font-size: .9em; }
li li { font-size: .95em; }
*, *:before, *:after {
box-sizing: inherit;
}
pre, img { max-width: 100%; }
pre, pre:hover {
white-space: pre-wrap;
word-break: break-all;
}
pre code {
display: block;
overflow-x: auto;
}
code { font-family: 'DejaVu Sans Mono', 'Droid Sans Mono', 'Lucida Console', Consolas, Monaco, monospace; }
:not(pre) > code, code[class] { background-color: #F8F8F8; }
code.language-undefined, pre > code:not([class]) {
background-color: inherit;
border: 1px solid #eee;
}
table {
margin: auto;
border-top: 1px solid #666;
}
table thead th { border-bottom: 1px solid #ddd; }
th, td { padding: 5px; }
thead, tfoot, tr:nth-child(even) { background: #eee; }
blockquote {
color: #666;
margin: 0;
padding-left: 1em;
border-left: 0.5em solid #eee;
}
hr, .footnotes::before { border: 1px dashed #ddd; }
.frontmatter { text-align: center; }
#TOC .numbered li { list-style: none; }
#TOC .numbered { padding-left: 0; }
#TOC .numbered ul { padding-left: 1em; }
table, .body h2 { border-bottom: 1px solid #666; }
.body .appendix, .appendix ~ h2 { border-bottom-style: dashed; }
.footnote-ref a::before { content: "["; }
.footnote-ref a::after { content: "]"; }
section.footnotes::before {
content: "";
display: block;
max-width: 20em;
}
@media print {
body {
font-size: 12pt;
max-width: 100%;
}
tr, img { page-break-inside: avoid; }
}
@media only screen and (min-width: 992px) {
pre { white-space: pre; }
}
</style>
</head>
<body>
<div class="frontmatter">
<div class="title"><h1>Efficient reshaping using data.tables</h1></div>
<div class="author"><h2></h2></div>
<div class="date"><h3>2024-10-04</h3></div>
</div>
<div class="body">
<p>Esta viñeta analiza el uso predeterminado de las funciones de remodelación <code>melt</code> (de ancho a largo) y <code>dcast</code> (de largo a ancho) para <em>data.tables</em>, así como las <strong>nuevas funcionalidades extendidas</strong> de fusión y conversión en <em>múltiples columnas</em> disponibles a partir de <code>v1.9.6</code>.</p>
<hr />
<h2 id="datos">Datos</h2>
<p>Cargaremos los conjuntos de datos directamente dentro de las secciones.</p>
<h2 id="introducci-n">Introducción</h2>
<p>Las funciones <code>melt</code> y <code>dcast</code> para <code>data.table</code>s sirven para cambiar la forma de ancho a largo y de largo a ancho, respectivamente; las implementaciones están diseñadas específicamente teniendo en mente grandes datos en memoria (por ejemplo, 10 Gb).</p>
<p>En esta viñeta, vamos a</p>
<ol>
<li>
<p>Primero, observe brevemente la conversión predeterminada de <code>melt</code> y <code>dcast</code> de <code>data.table</code> para convertirlas de formato <em>ancho</em> a <em>largo</em> y <em>viceversa</em></p>
</li>
<li>
<p>Analice los escenarios en los que las funcionalidades actuales se vuelven engorrosas e ineficientes</p>
</li>
<li>
<p>Por último, observe las nuevas mejoras en los métodos <code>melt</code> y <code>dcast</code> para que <code>data.table</code> pueda manejar múltiples columnas simultáneamente.</p>
</li>
</ol>
<p>Las funcionalidades extendidas están en línea con la filosofía de <code>data.table</code> de realizar operaciones de manera eficiente y sencilla.</p>
<h2 id="1-funcionalidad-predeterminada">1. Funcionalidad predeterminada</h2>
<h3 id="a-fusi-n-de-data-table-de-ancho-a-largo">a) <code>fusión</code> de `data.table`` (de ancho a largo)</h3>
<p>Supongamos que tenemos una <code>data.table</code> (datos artificiales) como se muestra a continuación:</p>
<pre><code class="language-r">s1 <- "family_id age_mother dob_child1 dob_child2 dob_child3
1 30 1998-11-26 2000-01-29 NA
2 27 1996-06-22 NA NA
3 26 2002-07-11 2004-04-05 2007-09-02
4 32 2004-10-10 2009-08-27 2012-07-21
5 29 2000-12-05 2005-02-28 NA"
DT <- fread(s1)
DT
# family_id age_mother dob_child1 dob_child2 dob_child3
# <int> <int> <IDat> <IDat> <IDat>
# 1: 1 30 1998-11-26 2000-01-29 <NA>
# 2: 2 27 1996-06-22 <NA> <NA>
# 3: 3 26 2002-07-11 2004-04-05 2007-09-02
# 4: 4 32 2004-10-10 2009-08-27 2012-07-21
# 5: 5 29 2000-12-05 2005-02-28 <NA>
## dob stands for date of birth.
str(DT)
# Classes 'data.table' and 'data.frame': 5 obs. of 5 variables:
# $ family_id : int 1 2 3 4 5
# $ age_mother: int 30 27 26 32 29
# $ dob_child1: IDate, format: "1998-11-26" "1996-06-22" "2002-07-11" ...
# $ dob_child2: IDate, format: "2000-01-29" NA "2004-04-05" ...
# $ dob_child3: IDate, format: NA NA "2007-09-02" ...
# - attr(*, ".internal.selfref")=<externalptr>
</code></pre>
<h4 id="convertir-dt-a-formato-largo-donde-cada-dob-es-una-observaci-n-separada">- Convertir ‘DT’ a formato <em>largo</em> donde cada ‘dob’ es una observación separada.</h4>
<p>Podríamos lograr esto usando <code>melt()</code> especificando los argumentos <code>id.vars</code> y <code>measure.vars</code> de la siguiente manera:</p>
<pre><code class="language-r">DT.m1 = melt(DT, id.vars = c("family_id", "age_mother"),
measure.vars = c("dob_child1", "dob_child2", "dob_child3"))
DT.m1
# family_id age_mother variable value
# <int> <int> <fctr> <IDat>
# 1: 1 30 dob_child1 1998-11-26
# 2: 2 27 dob_child1 1996-06-22
# 3: 3 26 dob_child1 2002-07-11
# 4: 4 32 dob_child1 2004-10-10
# 5: 5 29 dob_child1 2000-12-05
# 6: 1 30 dob_child2 2000-01-29
# 7: 2 27 dob_child2 <NA>
# 8: 3 26 dob_child2 2004-04-05
# 9: 4 32 dob_child2 2009-08-27
# 10: 5 29 dob_child2 2005-02-28
# 11: 1 30 dob_child3 <NA>
# 12: 2 27 dob_child3 <NA>
# 13: 3 26 dob_child3 2007-09-02
# 14: 4 32 dob_child3 2012-07-21
# 15: 5 29 dob_child3 <NA>
str(DT.m1)
# Classes 'data.table' and 'data.frame': 15 obs. of 4 variables:
# $ family_id : int 1 2 3 4 5 1 2 3 4 5 ...
# $ age_mother: int 30 27 26 32 29 30 27 26 32 29 ...
# $ variable : Factor w/ 3 levels "dob_child1","dob_child2",..: 1 1 1 1 1 2 2 2 2 2 ...
# $ value : IDate, format: "1998-11-26" "1996-06-22" "2002-07-11" ...
# - attr(*, ".internal.selfref")=<externalptr>
</code></pre>
<ul>
<li>
<p><code>measure.vars</code> especifica el conjunto de columnas que nos gustaría contraer (o combinar) juntas.</p>
</li>
<li>
<p>También podemos especificar <em>posiciones</em> de columnas en lugar de <em>nombres</em>.</p>
</li>
<li>
<p>De manera predeterminada, la columna <code>variable</code> es del tipo <code>factor</code>. Establezca el argumento <code>variable.factor</code> en <code>FALSO</code> si desea devolver un vector de <em><code>carácter</code></em> en su lugar.</p>
</li>
<li>
<p>De manera predeterminada, las columnas fundidas se denominan automáticamente <code>variable</code> y <code>valor</code>.</p>
</li>
<li>
<p><code>melt</code> conserva los atributos de la columna en el resultado.</p>
</li>
</ul>
<h4 id="nombra-las-columnas-variable-y-valor-como-hijo-y-dob-respectivamente">- Nombra las columnas <code>variable</code> y <code>valor</code> como <code>hijo</code> y <code>dob</code> respectivamente</h4>
<pre><code class="language-r">DT.m1 = melt(DT, measure.vars = c("dob_child1", "dob_child2", "dob_child3"),
variable.name = "child", value.name = "dob")
DT.m1
# family_id age_mother child dob
# <int> <int> <fctr> <IDat>
# 1: 1 30 dob_child1 1998-11-26
# 2: 2 27 dob_child1 1996-06-22
# 3: 3 26 dob_child1 2002-07-11
# 4: 4 32 dob_child1 2004-10-10
# 5: 5 29 dob_child1 2000-12-05
# 6: 1 30 dob_child2 2000-01-29
# 7: 2 27 dob_child2 <NA>
# 8: 3 26 dob_child2 2004-04-05
# 9: 4 32 dob_child2 2009-08-27
# 10: 5 29 dob_child2 2005-02-28
# 11: 1 30 dob_child3 <NA>
# 12: 2 27 dob_child3 <NA>
# 13: 3 26 dob_child3 2007-09-02
# 14: 4 32 dob_child3 2012-07-21
# 15: 5 29 dob_child3 <NA>
</code></pre>
<ul>
<li>
<p>De manera predeterminada, cuando falta una de las <code>id.vars</code> o <code>measure.vars</code>, el resto de las columnas se <em>asigna automáticamente</em> al argumento faltante.</p>
</li>
<li>
<p>Cuando no se especifican ni <code>id.vars</code> ni <code>measure.vars</code>, como se menciona en <code>?melt</code>, todas las columnas <em>no</em> <code>numéricas</code>, <code>enteras</code>, <code>lógicas</code> se asignarán a <code>id.vars</code>.</p>
<p>In addition, a warning message is issued highlighting the columns that are automatically considered to be <code>id.vars</code>.</p>
</li>
</ul>
<h3 id="b-dcast-de-data-table-de-largo-a-ancho">b) <code>dcast</code> de <code>data.table</code> (de largo a ancho)</h3>
<p>En la sección anterior, vimos cómo pasar del formato ancho al formato largo. Veamos la operación inversa en esta sección.</p>
<h4 id="c-mo-podemos-volver-a-la-tabla-de-datos-original-dt-desde-dt-m1">- ¿Cómo podemos volver a la tabla de datos original <code>DT</code> desde <code>DT.m1</code>?</h4>
<p>Es decir, nos gustaría recopilar todas las observaciones de <em>child</em> correspondientes a cada <code>family_id, age_mother</code> juntas en la misma fila. Podemos lograrlo usando <code>dcast</code> de la siguiente manera:</p>
<pre><code class="language-r">dcast(DT.m1, family_id + age_mother ~ child, value.var = "dob")
# Key: <family_id, age_mother>
# family_id age_mother dob_child1 dob_child2 dob_child3
# <int> <int> <IDat> <IDat> <IDat>
# 1: 1 30 1998-11-26 2000-01-29 <NA>
# 2: 2 27 1996-06-22 <NA> <NA>
# 3: 3 26 2002-07-11 2004-04-05 2007-09-02
# 4: 4 32 2004-10-10 2009-08-27 2012-07-21
# 5: 5 29 2000-12-05 2005-02-28 <NA>
</code></pre>
<ul>
<li>
<p><code>dcast</code> utiliza la interfaz <em>formula</em>. Las variables del <em>lado izquierdo</em> de la fórmula representan las variables <em>id</em> y del <em>lado derecho</em> las variables <em>measure</em>.</p>
</li>
<li>
<p><code>value.var</code> indica la columna que se debe completar al convertir a formato ancho.</p>
</li>
<li>
<p><code>dcast</code> también intenta preservar los atributos en el resultado siempre que sea posible.</p>
</li>
</ul>
<h4 id="a-partir-de-dt-m1-c-mo-podemos-obtener-el-n-mero-de-hijos-en-cada-familia">- A partir de <code>DT.m1</code>, ¿cómo podemos obtener el número de hijos en cada familia?</h4>
<p>También puede pasar una función para agregar en <code>dcast</code> con el argumento <code>fun.agregate</code>. Esto es particularmente esencial cuando la fórmula proporcionada no identifica una sola observación para cada celda.</p>
<pre><code class="language-r">dcast(DT.m1, family_id ~ ., fun.agg = function(x) sum(!is.na(x)), value.var = "dob")
# Key: <family_id>
# family_id .
# <int> <int>
# 1: 1 2
# 2: 2 1
# 3: 3 3
# 4: 4 3
# 5: 5 2
</code></pre>
<p>Consulte <code>?dcast</code> para obtener otros argumentos útiles y ejemplos adicionales.</p>
<h2 id="2-limitaciones-de-los-m-todos-actuales-de-fusi-n-desintegraci-n">2. Limitaciones de los métodos actuales de «fusión/desintegración»</h2>
<p>Hasta ahora hemos visto características de <code>melt</code> y <code>dcast</code> que se implementan de manera eficiente para <code>data.table</code>s, utilizando la maquinaria interna de <code>data.table</code> (<em>ordenamiento rápido de radix</em>, <em>búsqueda binaria</em>, etc.).</p>
<p>Sin embargo, existen situaciones en las que podemos encontrarnos con la operación deseada que no se expresa de manera sencilla. Por ejemplo, considere la tabla <code>data.table</code> que se muestra a continuación:</p>
<pre><code class="language-r">s2 <- "family_id age_mother dob_child1 dob_child2 dob_child3 gender_child1 gender_child2 gender_child3
1 30 1998-11-26 2000-01-29 NA 1 2 NA
2 27 1996-06-22 NA NA 2 NA NA
3 26 2002-07-11 2004-04-05 2007-09-02 2 2 1
4 32 2004-10-10 2009-08-27 2012-07-21 1 1 1
5 29 2000-12-05 2005-02-28 NA 2 1 NA"
DT <- fread(s2)
DT
# family_id age_mother dob_child1 dob_child2 dob_child3 gender_child1 gender_child2 gender_child3
# <int> <int> <IDat> <IDat> <IDat> <int> <int> <int>
# 1: 1 30 1998-11-26 2000-01-29 <NA> 1 2 NA
# 2: 2 27 1996-06-22 <NA> <NA> 2 NA NA
# 3: 3 26 2002-07-11 2004-04-05 2007-09-02 2 2 1
# 4: 4 32 2004-10-10 2009-08-27 2012-07-21 1 1 1
# 5: 5 29 2000-12-05 2005-02-28 <NA> 2 1 NA
## 1 = female, 2 = male
</code></pre>
<p>Y desea combinar (<code>melt</code>) todas las columnas <code>dob</code> y <code>gender</code>. Con la funcionalidad actual, podemos hacer algo como esto:</p>
<pre><code class="language-r">DT.m1 = melt(DT, id = c("family_id", "age_mother"))
DT.m1[, c("variable", "child") := tstrsplit(variable, "_", fixed = TRUE)]
# family_id age_mother variable value child
# <int> <int> <char> <IDat> <char>
# 1: 1 30 dob 1998-11-26 child1
# 2: 2 27 dob 1996-06-22 child1
# 3: 3 26 dob 2002-07-11 child1
# 4: 4 32 dob 2004-10-10 child1
# 5: 5 29 dob 2000-12-05 child1
# 6: 1 30 dob 2000-01-29 child2
# 7: 2 27 dob <NA> child2
# 8: 3 26 dob 2004-04-05 child2
# 9: 4 32 dob 2009-08-27 child2
# 10: 5 29 dob 2005-02-28 child2
# 11: 1 30 dob <NA> child3
# 12: 2 27 dob <NA> child3
# 13: 3 26 dob 2007-09-02 child3
# 14: 4 32 dob 2012-07-21 child3
# 15: 5 29 dob <NA> child3
# 16: 1 30 gender 1970-01-02 child1
# 17: 2 27 gender 1970-01-03 child1
# 18: 3 26 gender 1970-01-03 child1
# 19: 4 32 gender 1970-01-02 child1
# 20: 5 29 gender 1970-01-03 child1
# 21: 1 30 gender 1970-01-03 child2
# 22: 2 27 gender <NA> child2
# 23: 3 26 gender 1970-01-03 child2
# 24: 4 32 gender 1970-01-02 child2
# 25: 5 29 gender 1970-01-02 child2
# 26: 1 30 gender <NA> child3
# 27: 2 27 gender <NA> child3
# 28: 3 26 gender 1970-01-02 child3
# 29: 4 32 gender 1970-01-02 child3
# 30: 5 29 gender <NA> child3
# family_id age_mother variable value child
DT.c1 = dcast(DT.m1, family_id + age_mother + child ~ variable, value.var = "value")
DT.c1
# Key: <family_id, age_mother, child>
# family_id age_mother child dob gender
# <int> <int> <char> <IDat> <IDat>
# 1: 1 30 child1 1998-11-26 1970-01-02
# 2: 1 30 child2 2000-01-29 1970-01-03
# 3: 1 30 child3 <NA> <NA>
# 4: 2 27 child1 1996-06-22 1970-01-03
# 5: 2 27 child2 <NA> <NA>
# 6: 2 27 child3 <NA> <NA>
# 7: 3 26 child1 2002-07-11 1970-01-03
# 8: 3 26 child2 2004-04-05 1970-01-03
# 9: 3 26 child3 2007-09-02 1970-01-02
# 10: 4 32 child1 2004-10-10 1970-01-02
# 11: 4 32 child2 2009-08-27 1970-01-02
# 12: 4 32 child3 2012-07-21 1970-01-02
# 13: 5 29 child1 2000-12-05 1970-01-03
# 14: 5 29 child2 2005-02-28 1970-01-02
# 15: 5 29 child3 <NA> <NA>
str(DT.c1) ## gender column is class IDate now!
# Classes 'data.table' and 'data.frame': 15 obs. of 5 variables:
# $ family_id : int 1 1 1 2 2 2 3 3 3 4 ...
# $ age_mother: int 30 30 30 27 27 27 26 26 26 32 ...
# $ child : chr "child1" "child2" "child3" "child1" ...
# $ dob : IDate, format: "1998-11-26" "2000-01-29" NA ...
# $ gender : IDate, format: "1970-01-02" "1970-01-03" NA ...
# - attr(*, ".internal.selfref")=<externalptr>
# - attr(*, "sorted")= chr [1:3] "family_id" "age_mother" "child"
</code></pre>
<h4 id="asuntos">Asuntos</h4>
<ol>
<li>
<p>Lo que queríamos hacer era combinar todas las columnas de tipo <code>dob</code> y <code>gender</code> respectivamente. En lugar de eso, estamos combinando <em>todo</em> y luego dividiendo todo. Creo que es fácil ver que es bastante indirecto (e ineficiente).</p>
<p>As an analogy, imagine you’ve a closet with four shelves of clothes and you’d like to put together the clothes from shelves 1 and 2 together (in 1), and 3 and 4 together (in 3). What we are doing is more or less to combine all the clothes together, and then split them back on to shelves 1 and 3!</p>
</li>
<li>
<p>Las columnas que se van a <code>melt</code> pueden ser de tipos diferentes, como en este caso (tipos <code>character</code> y <code>integer</code>). Al <code>melt</code> todas juntas, las columnas se convertirán en el resultado, como se explica en el mensaje de advertencia anterior y se muestra en la salida de <code>str(DT.c1)</code>, donde <code>gender</code> se ha convertido al tipo <em><code>character</code></em>.</p>
</li>
<li>
<p>Estamos generando una columna adicional dividiendo la columna <code>variable</code> en dos columnas, cuyo propósito es bastante críptico. Lo hacemos porque lo necesitamos para la <em>conversión</em> en el siguiente paso.</p>
</li>
<li>
<p>Finalmente, convertimos el conjunto de datos. Pero el problema es que es una operación que requiere mucho más trabajo computacional que <em>melt</em>. En concreto, requiere calcular el orden de las variables en la fórmula, y eso es costoso.</p>
</li>
</ol>
<p>De hecho, <code>stats::reshape</code> es capaz de realizar esta operación de una manera muy sencilla. Es una función extremadamente útil y a menudo subestimada. ¡Definitivamente deberías probarla!</p>
<h2 id="3-funcionalidad-mejorada-nueva">3. Funcionalidad mejorada (nueva)</h2>
<h3 id="a-fusi-n-mejorada">a) Fusión mejorada</h3>
<p>Como nos gustaría que <code>data.table</code> realice esta operación de manera sencilla y eficiente utilizando la misma interfaz, seguimos adelante e implementamos una <em>funcionalidad adicional</em>, donde podemos <code>fusionar</code> varias columnas <em>simultáneamente</em>.</p>
<h4 id="fundir-m-ltiples-columnas-simult-neamente">- <code>fundir</code> múltiples columnas simultáneamente</h4>
<p>La idea es bastante sencilla. Pasamos una lista de columnas a <code>measure.vars</code>, donde cada elemento de la lista contiene las columnas que deben combinarse.</p>
<pre><code class="language-r">colA = paste0("dob_child", 1:3)
colB = paste0("gender_child", 1:3)
DT.m2 = melt(DT, measure = list(colA, colB), value.name = c("dob", "gender"))
DT.m2
# family_id age_mother variable dob gender
# <int> <int> <fctr> <IDat> <int>
# 1: 1 30 1 1998-11-26 1
# 2: 2 27 1 1996-06-22 2
# 3: 3 26 1 2002-07-11 2
# 4: 4 32 1 2004-10-10 1
# 5: 5 29 1 2000-12-05 2
# 6: 1 30 2 2000-01-29 2
# 7: 2 27 2 <NA> NA
# 8: 3 26 2 2004-04-05 2
# 9: 4 32 2 2009-08-27 1
# 10: 5 29 2 2005-02-28 1
# 11: 1 30 3 <NA> NA
# 12: 2 27 3 <NA> NA
# 13: 3 26 3 2007-09-02 1
# 14: 4 32 3 2012-07-21 1
# 15: 5 29 3 <NA> NA
str(DT.m2) ## col type is preserved
# Classes 'data.table' and 'data.frame': 15 obs. of 5 variables:
# $ family_id : int 1 2 3 4 5 1 2 3 4 5 ...
# $ age_mother: int 30 27 26 32 29 30 27 26 32 29 ...
# $ variable : Factor w/ 3 levels "1","2","3": 1 1 1 1 1 2 2 2 2 2 ...
# $ dob : IDate, format: "1998-11-26" "1996-06-22" "2002-07-11" ...
# $ gender : int 1 2 2 1 2 2 NA 2 1 1 ...
# - attr(*, ".internal.selfref")=<externalptr>
</code></pre>
<ul>
<li>
<p>Podemos eliminar la columna <code>variable</code> si es necesario.</p>
</li>
<li>
<p>La funcionalidad está implementada completamente en C y, por lo tanto, es <em>rápida</em> y <em>eficiente en el uso de la memoria</em>, además de ser <em>sencilla</em>.</p>
</li>
</ul>
<h4 id="usando-patrones">- Usando <code>patrones()</code></h4>
<p>Por lo general, en estos problemas, las columnas que queremos fundir se pueden distinguir por un patrón común. Podemos utilizar la función <code>patterns()</code>, implementada por conveniencia, para proporcionar expresiones regulares para las columnas que se combinarán. La operación anterior se puede reescribir como:</p>
<pre><code class="language-r">DT.m2 = melt(DT, measure = patterns("^dob", "^gender"), value.name = c("dob", "gender"))
DT.m2
# family_id age_mother variable dob gender
# <int> <int> <fctr> <IDat> <int>
# 1: 1 30 1 1998-11-26 1
# 2: 2 27 1 1996-06-22 2
# 3: 3 26 1 2002-07-11 2
# 4: 4 32 1 2004-10-10 1
# 5: 5 29 1 2000-12-05 2
# 6: 1 30 2 2000-01-29 2
# 7: 2 27 2 <NA> NA
# 8: 3 26 2 2004-04-05 2
# 9: 4 32 2 2009-08-27 1
# 10: 5 29 2 2005-02-28 1
# 11: 1 30 3 <NA> NA
# 12: 2 27 3 <NA> NA
# 13: 3 26 3 2007-09-02 1
# 14: 4 32 3 2012-07-21 1
# 15: 5 29 3 <NA> NA
</code></pre>
<h4 id="usar-measure-para-especificar-measure-vars-a-trav-s-de-un-separador-o-patr-n">- Usar <code>measure()</code> para especificar <code>measure.vars</code> a través de un separador o patrón</h4>
<p>Si, como en los datos anteriores, las columnas de entrada que se van a fundir tienen nombres regulares, entonces podemos usar <code>measure</code>, que permite especificar las columnas que se van a fundir mediante un separador o una expresión regular. Por ejemplo, considere los datos del iris:</p>
<pre><code class="language-r">(two.iris = data.table(datasets::iris)[c(1,150)])
# Sepal.Length Sepal.Width Petal.Length Petal.Width Species
# <num> <num> <num> <num> <fctr>
# 1: 5.1 3.5 1.4 0.2 setosa
# 2: 5.9 3.0 5.1 1.8 virginica
</code></pre>
<p>Los datos del iris tienen cuatro columnas numéricas con una estructura regular: primero la parte de la flor, luego un punto y luego la dimensión de la medida. Para especificar que queremos fusionar esas cuatro columnas, podemos usar <code>measure</code> con <code>sep="."</code>, lo que significa usar <code>strsplit</code> en todos los nombres de columna; las columnas que resulten en la cantidad máxima de grupos después de la división se usarán como <code>measure.vars</code>:</p>
<pre><code class="language-r">melt(two.iris, measure.vars = measure(part, dim, sep="."))
# Species part dim value
# <fctr> <char> <char> <num>
# 1: setosa Sepal Length 5.1
# 2: virginica Sepal Length 5.9
# 3: setosa Sepal Width 3.5
# 4: virginica Sepal Width 3.0
# 5: setosa Petal Length 1.4
# 6: virginica Petal Length 5.1
# 7: setosa Petal Width 0.2
# 8: virginica Petal Width 1.8
</code></pre>
<p>Los primeros dos argumentos de <code>measure</code> en el código anterior (<code>part</code> y <code>dim</code>) se utilizan para nombrar las columnas de salida; la cantidad de argumentos debe ser igual a la cantidad máxima de grupos después de dividir con <code>sep</code>.</p>
<p>Si queremos dos columnas de valores, una para cada parte, podemos usar la palabra clave especial <code>value.name</code>, lo que significa generar una columna de valores para cada nombre único encontrado en ese grupo:</p>
<pre><code class="language-r">melt(two.iris, measure.vars = measure(value.name, dim, sep="."))
# Species dim Sepal Petal
# <fctr> <char> <num> <num>
# 1: setosa Length 5.1 1.4
# 2: virginica Length 5.9 5.1
# 3: setosa Width 3.5 0.2
# 4: virginica Width 3.0 1.8
</code></pre>
<p>Con el código anterior obtenemos una columna de valores por cada parte de la flor. Si, en cambio, queremos una columna de valores para cada dimensión de medida, podemos hacer lo siguiente:</p>
<pre><code class="language-r">melt(two.iris, measure.vars = measure(part, value.name, sep="."))
# Species part Length Width
# <fctr> <char> <num> <num>
# 1: setosa Sepal 5.1 3.5
# 2: virginica Sepal 5.9 3.0
# 3: setosa Petal 1.4 0.2
# 4: virginica Petal 5.1 1.8
</code></pre>
<p>Volviendo al ejemplo de los datos con familias e hijos, podemos ver un uso más complejo de <code>measure</code>, que involucra una función que se utiliza para convertir los valores de la cadena <code>child</code> en números enteros:</p>
<pre><code class="language-r">DT.m3 = melt(DT, measure = measure(value.name, child=as.integer, sep="_child"))
DT.m3
# family_id age_mother child dob gender
# <int> <int> <int> <IDat> <int>
# 1: 1 30 1 1998-11-26 1
# 2: 2 27 1 1996-06-22 2
# 3: 3 26 1 2002-07-11 2
# 4: 4 32 1 2004-10-10 1
# 5: 5 29 1 2000-12-05 2
# 6: 1 30 2 2000-01-29 2
# 7: 2 27 2 <NA> NA
# 8: 3 26 2 2004-04-05 2
# 9: 4 32 2 2009-08-27 1
# 10: 5 29 2 2005-02-28 1
# 11: 1 30 3 <NA> NA
# 12: 2 27 3 <NA> NA
# 13: 3 26 3 2007-09-02 1
# 14: 4 32 3 2012-07-21 1
# 15: 5 29 3 <NA> NA
</code></pre>
<p>En el código anterior, usamos <code>sep="_child"</code>, lo que da como resultado la fusión de solo las columnas que contienen esa cadena (seis nombres de columnas divididos en dos grupos cada uno). El argumento <code>child=as.integer</code> significa que el segundo grupo dará como resultado una columna de salida llamada <code>child</code> con valores definidos al insertar las cadenas de caracteres de ese grupo en la función <code>as.integer</code>.</p>
<p>Finalmente, consideramos un ejemplo (tomado del paquete tidyr) donde necesitamos definir los grupos usando una expresión regular en lugar de un separador.</p>
<pre><code class="language-r">(who <- data.table(id=1, new_sp_m5564=2, newrel_f65=3))
# id new_sp_m5564 newrel_f65
# <num> <num> <num>
# 1: 1 2 3
melt(who, measure.vars = measure(
diagnosis, gender, ages, pattern="new_?(.*)_(.)(.*)"))
# id diagnosis gender ages value
# <num> <char> <char> <char> <num>
# 1: 1 sp m 5564 2
# 2: 1 rel f 65 3
</code></pre>
<p>Al utilizar el argumento <code>patrón</code>, debe ser una expresión regular compatible con Perl que contenga la misma cantidad de grupos de captura (subexpresiones entre paréntesis) que la cantidad de otros argumentos (nombres de grupos). El código siguiente muestra cómo utilizar una expresión regular más compleja con cinco grupos, dos columnas de salida numérica y una función de conversión de tipo anónima.</p>
<pre><code class="language-r">melt(who, measure.vars = measure(
diagnosis, gender, ages,
ymin=as.numeric,
ymax=function(y) ifelse(nzchar(y), as.numeric(y), Inf),
pattern="new_?(.*)_(.)(([0-9]{2})([0-9]{0,2}))"
))
# id diagnosis gender ages ymin ymax value
# <num> <char> <char> <char> <num> <num> <num>
# 1: 1 sp m 5564 55 64 2
# 2: 1 rel f 65 65 Inf 3
</code></pre>
<h3 id="b-dcast-mejorado">b) <code>dcast</code> mejorado</h3>
<p>¡Genial! Ahora podemos fusionar varias columnas simultáneamente. Ahora, dado el conjunto de datos <code>DT.m2</code> como se muestra arriba, ¿cómo podemos volver al mismo formato que los datos originales con los que comenzamos?</p>
<p>Si usamos la funcionalidad actual de <code>dcast</code>, entonces tendríamos que realizar la conversión dos veces y vincular los resultados. Pero eso es, una vez más, demasiado verboso, no es sencillo y también es ineficiente.</p>
<h4 id="conversi-n-de-m-ltiples-value-var-simult-neamente">- Conversión de múltiples <code>value.var</code> simultáneamente</h4>
<p>Ahora podemos proporcionar <strong>múltiples columnas <code>value.var</code></strong> a <code>dcast</code> para <code>data.table</code> directamente para que las operaciones se realicen de manera interna y eficiente.</p>
<pre><code class="language-r">## new 'cast' functionality - multiple value.vars
DT.c2 = dcast(DT.m2, family_id + age_mother ~ variable, value.var = c("dob", "gender"))
DT.c2
# Key: <family_id, age_mother>
# family_id age_mother dob_1 dob_2 dob_3 gender_1 gender_2 gender_3
# <int> <int> <IDat> <IDat> <IDat> <int> <int> <int>
# 1: 1 30 1998-11-26 2000-01-29 <NA> 1 2 NA
# 2: 2 27 1996-06-22 <NA> <NA> 2 NA NA
# 3: 3 26 2002-07-11 2004-04-05 2007-09-02 2 2 1
# 4: 4 32 2004-10-10 2009-08-27 2012-07-21 1 1 1
# 5: 5 29 2000-12-05 2005-02-28 <NA> 2 1 NA
</code></pre>
<ul>
<li>
<p>Los atributos se conservan en el resultado siempre que sea posible.</p>
</li>
<li>
<p>Todo se gestiona internamente y de manera eficiente. Además de ser rápido, también es muy eficiente en el uso de la memoria.</p>
</li>
</ul>
<h1></h1>
<h4 id="varias-funciones-para-fun-agregate">Varias funciones para <code>fun.agregate</code>:</h4>
<p>También puede proporcionar <em>múltiples funciones</em> a <code>fun.agregate</code> para <code>dcast</code> para <em>data.tables</em>. Consulte los ejemplos en <code>?dcast</code> que ilustran esta funcionalidad.</p>
<h1></h1>
<hr />
</div>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-core.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js" defer></script>
</body>
</html>