diff --git a/vignettes/es/datatable-benchmarking.Rmd b/vignettes/es/datatable-benchmarking.Rmd
new file mode 100644
index 000000000..6cdee88c9
--- /dev/null
+++ b/vignettes/es/datatable-benchmarking.Rmd
@@ -0,0 +1,147 @@
+---
+title: "Benchmarking con data.table"
+date: "`{r} Sys.Date()`"
+output:
+ litedown::html_format:
+ options:
+ toc: true
+ number_sections: true
+vignette: >
+ %\VignetteIndexEntry{Benchmarking data.table}
+ %\VignetteEngine{litedown::vignette}
+ \usepackage[utf8]{inputenc}
+---
+
+```{r, echo = FALSE, message = FALSE}
+library(data.table)
+litedown::reactor(comment = "# ")
+```
+
+
+
+```{r, echo=FALSE, file='../_translation_links.R'}
+```
+
+`{r} .write.translation.links("Las traducciones de este documento están disponibles en: %s")`
+
+Este documento sirve como guía para medir el rendimiento de `data.table`. Es un lugar único para documentar las mejores prácticas y los errores que se deben evitar.
+
+# fread: borrar cachés
+
+Idealmente, cada llamada a «fread» debería ejecutarse en una sesión nueva con los siguientes comandos antes de la ejecución de R. Esto borra el archivo de caché del sistema operativo en la RAM y la caché del disco duro
+
+```sh
+free -g
+sudo sh -c 'echo 3 >/proc/sys/vm/drop_caches'
+sudo lshw -class disk
+sudo hdparm -t /dev/sda
+```
+
+Al comparar `fread` con soluciones que no son de R, tenga en cuenta que R requiere que los valores de las columnas de caracteres se agreguen a la caché de cadenas global de R. Esto requiere tiempo al leer datos, pero las operaciones posteriores se benefician, ya que las cadenas de caracteres ya se han almacenado en caché. Por lo tanto, además de cronometrar tareas aisladas (como `fread` por sí sola), conviene comparar el tiempo total de un flujo de trabajo integral de tareas, como la lectura de datos, su manipulación y la generación del resultado final.
+
+# subset: umbral para la optimización del índice en consultas compuestas
+
+La optimización de índice para consultas de filtros compuestos no se utilizará cuando el producto cruzado de los elementos proporcionados para filtrar exceda 1e4 elementos.
+
+```r
+DT = data.table(V1=1:10, V2=1:10, V3=1:10, V4=1:10)
+setindex(DT)
+v = c(1L, rep(11L, 9))
+length(v)^4 # cross product of elements in filter
+#[1] 10000 # <= 10000
+DT[V1 %in% v & V2 %in% v & V3 %in% v & V4 %in% v, verbose=TRUE]
+#Optimized subsetting with index 'V1__V2__V3__V4'
+#on= matches existing index, using index
+#Starting bmerge ...done in 0.000sec
+#...
+v = c(1L, rep(11L, 10))
+length(v)^4 # cross product of elements in filter
+#[1] 14641 # > 10000
+DT[V1 %in% v & V2 %in% v & V3 %in% v & V4 %in% v, verbose=TRUE]
+#Subsetting optimization disabled because the cross-product of RHS values exceeds 1e4, causing memory problems.
+#...
+```
+
+# subset: evaluación comparativa basada en índices
+
+Para mayor comodidad, `data.table` crea automáticamente un índice en los campos que se utilizan para filtra datos. Esto añadirá algo de sobrecarga al primer filtrado en campos específicos, pero reduce considerablemente el tiempo de consulta de esas columnas en ejecuciones posteriores. Para medir la velocidad, la mejor manera es medir la creación y la consulta de índices por separado. Con estos tiempos, es fácil decidir cuál es la estrategia óptima para su caso de uso. Para controlar el uso del índice, utilice las siguientes opciones:
+
+```r
+options(datatable.auto.index=TRUE)
+options(datatable.use.index=TRUE)
+```
+
+- `use.index=FALSE` forzará la consulta a no usar índices incluso si existen, pero las claves existentes aún se usan para la optimización.
+- `auto.index=FALSE` deshabilita la creación automática de índices al filtrar datos no indexados, pero si los índices se crearon antes de que se estableciera esta opción, o explícitamente al llamar a `setindex`, aún se usarán para la optimización.
+
+Otras dos opciones controlan la optimización a nivel global, incluido el uso de índices:
+
+```r
+options(datatable.optimize=2L)
+options(datatable.optimize=3L)
+```
+
+`options(datatable.optimize=2L)` desactivará por completo la optimización de filtros, mientras que `options(datatable.optimize=3L)` la reactivará. Estas opciones afectan a muchas más optimizaciones y, por lo tanto, no deben usarse cuando solo se necesita controlar los índices. Más información en `?datatable.optimize`.
+
+# Operaciones *por referencia*
+
+Al comparar funciones `set*`, solo tiene sentido medir la primera ejecución. Estas funciones actualizan su entrada por referencia, por lo que las ejecuciones posteriores utilizarán la `data.table` ya procesada, lo que sesgará los resultados
+
+Para proteger su `data.table` de la actualización por referencia, puede usar las funciones `copy` o `data.table:::shallow`. Tenga en cuenta que `copy` puede ser muy costoso, ya que requiere duplicar el objeto completo. Es poco probable que queramos incluir el tiempo de duplicación en la tarea que estamos evaluando.
+
+# Intentar comparar los procesos atómicos
+
+Si su punto de referencia está destinado a ser publicado, será mucho más esclarecedor si lo divide para medir el tiempo de los procesos atómicos. De esta manera, sus lectores pueden ver cuánto tiempo se dedicó a leer los datos de la fuente, limpiarlos, transformarlos realmente y exportar los resultados. Por supuesto, si su punto de referencia está destinado a presentar un *flujo de trabajo de extremo a extremo*, entonces tiene todo el sentido presentar el tiempo general. Sin embargo, separar el tiempo de los pasos individuales es útil para comprender qué pasos son los principales cuellos de botella de un flujo de trabajo. Hay otros casos en los que el punto de referencia atómico podría no ser deseable, por ejemplo, al *leer un csv*, seguido de *agrupar*. R requiere llenar *la caché de cadena global de R*, lo que agrega sobrecarga adicional al importar datos de caracteres a una sesión de R. Por otro lado, la *caché de cadena global* podría acelerar procesos como *agrupar*. En tales casos, al comparar R con otros lenguajes, podría ser útil incluir el tiempo total.
+
+# Evite la coerción de clase
+
+A menos que esto sea lo que realmente quiera medir, debe preparar objetos de entrada de la clase esperada para cada herramienta que esté evaluando
+
+# evitar `microbenchmark(..., times=100)`
+
+Repetir un benchmark muchas veces no suele ofrecer la imagen más clara para las herramientas de procesamiento de datos. Por supuesto, tiene mucho sentido para cálculos más atómicos, pero esta no es una buena representación de la forma más común en que se utilizarán realmente estas herramientas, es decir, para las tareas de procesamiento de datos, que consisten en lotes de transformaciones proporcionadas secuencialmente, cada una ejecutada una vez. Matt dijo una vez:
+
+> Soy muy cauteloso con los puntos de referencia medidos en tiempos inferiores a 1 segundo. Prefiero 10 segundos o más para una sola ejecución, lo que se logra aumentando el tamaño de los datos. Un recuento de repeticiones de 500 es alarmante. De 3 a 5 ejecuciones deberían ser suficientes para convencer con datos más grandes. La sobrecarga de llamadas y el tiempo de recolección de basura afectan las inferencias a esta escala tan pequeña.
+
+Esto es muy válido. Cuanto menor sea la medición de tiempo, mayor será el ruido relativo. El ruido se genera por el envío de métodos, la inicialización de paquetes/clases, etc. El punto de referencia debe centrarse principalmente en casos de uso reales.
+
+# procesamiento multiproceso
+
+Uno de los principales factores que probablemente afecte a los tiempos es el número de subprocesos disponibles para su sesión de R. En versiones recientes de `data.table`, algunas funciones están paralelizadas. Puede controlar el número de subprocesos que desea utilizar con `setDTthreads`
+
+```r
+setDTthreads(0) # use all available cores (default)
+getDTthreads() # check how many cores are currently used
+```
+
+# Dentro de un bucle, prefiera `set` en lugar de `:=`
+
+A menos que esté utilizando el índice al realizar una *subasignación por referencia*, debería preferir la función `set` que no impone la sobrecarga de la llamada al método `[.data.table`.
+
+```r
+DT = data.table(a=3:1, b=letters[1:3])
+setindex(DT, a)
+
+# for (...) { # imagine loop here
+
+ DT[a==2L, b := "z"] # sub-assign by reference, uses index
+ DT[, d := "z"] # not sub-assign by reference, not uses index and adds overhead of `[.data.table`
+ set(DT, j="d", value="z") # no `[.data.table` overhead, but no index yet, till #1196
+
+# }
+```
+
+# Dentro de un bucle, prefiera `setDT` en lugar de `data.table()`
+
+A partir de ahora, `data.table()` tiene una sobrecarga, por lo tanto, dentro de los bucles se prefiere utilizar `as.data.table()` o `setDT()` en una lista válida.
diff --git a/vignettes/es/datatable-faq.Rmd b/vignettes/es/datatable-faq.Rmd
new file mode 100644
index 000000000..c3e2df970
--- /dev/null
+++ b/vignettes/es/datatable-faq.Rmd
@@ -0,0 +1,674 @@
+---
+title: "Preguntas frecuentes sobre data.table"
+date: "`{r} Sys.Date()`"
+output:
+ litedown::html_format:
+ options:
+ toc: true
+ number_sections: true
+vignette: >
+ %\VignetteIndexEntry{Frequently Asked Questions about data.table}
+ %\VignetteEngine{litedown::vignette}
+ \usepackage[utf8]{inputenc}
+---
+
+
+
+```{r, echo=FALSE, file='../_translation_links.R'}
+```
+
+`{r} .write.translation.links("Las traducciones de este documento están disponibles en: %s")`
+
+```{r, echo = FALSE, message = FALSE}
+library(data.table)
+litedown::reactor(comment = "# ")
+.old.th = setDTthreads(1)
+```
+
+La primera sección, Preguntas frecuentes para principiantes, está diseñada para leerse en orden, de principio a fin. Simplemente está escrita en estilo FAQ para facilitar su comprensión. No se trata de las preguntas más frecuentes. Una mejor manera de comprobarlo es consultar Stack Overflow.
+
+Estas preguntas frecuentes son de lectura obligatoria y se consideran documentación esencial. No haga preguntas en Stack Overflow ni plantee problemas en GitHub hasta que las haya leído. Todos sabemos que no las ha leído cuando pregunta. Así que, si pregunta y no las ha leído, no use su nombre real.
+
+Este documento se ha revisado rápidamente debido a los cambios en la versión 1.9.8, publicada en noviembre de 2016. Por favor, envíe solicitudes de incorporación de cambios para corregir errores o realizar mejoras. Si alguien sabe por qué la tabla de contenido aparece tan estrecha y compacta al ser mostrada por CRAN, por favor, infórmenos. Este documento solía estar en formato PDF y recientemente lo cambiamos a HTML.
+
+# Preguntas frecuentes para principiantes
+
+## ¿Por qué `DT[ , 5]` y `DT[2, 5]` devuelven un data.table de 1 columna en lugar de vectores como `data.frame`? {#j-num}
+
+Para mantener la coherencia, al usar data.table en funciones que aceptan entradas variables, se puede confiar en que `DT[...]` devolverá un data.table. No es necesario recordar incluir `drop=FALSE` como en data.frame. data.table se lanzó por primera vez en 2006 y esta diferencia con data.frame ha sido una característica desde el principio.
+
+Quizás haya oído que, en general, es una mala práctica referirse a las columnas por número en lugar de nombre. Si su colega lee tu código más tarde, puede que tenga que buscar por todas partes para encontrar la columna número 5. Si usted o él cambian el orden de las columnas en los niveles superiores de tu programa R, podría obtener resultados erróneos sin previo aviso ni error si se olvida modificar todos los lugares del código que hacen referencia a la columna número 5. La culpa es suya, no de R ni de data.table. Es realmente malo. Por favor, no lo haga. Es el mismo mantra de los desarrolladores profesionales de SQL: nunca uses `select *`, selecciona siempre explícitamente por nombre de columna para, al menos, intentar ser robusto ante futuros cambios.
+
+Supongamos que la columna 5 se llama `"region"` y debe extraerla como un vector, no como un data.table. Es más robusto usar el nombre de la columna y escribir `DT$region` o `DT[["region"]]`; es decir, lo mismo que la base R. Se recomienda usar `$` y `[[` de la base R en data.table. No se recomienda su uso cuando se combina con `<-` para asignar (use `:=` en su lugar), sino simplemente para seleccionar una sola columna por nombre. Sin embargo, una diferencia clave es que DT$col y DT[['col']] pueden devolver una referencia, mientras que DT[, col] siempre devuelve una copia. Esto puede tener consecuencias importantes y se explica en `vignette("datatable-reference-semantics", package="data.table")`.
+
+Hay circunstancias en las que referirse a una columna por número parece ser la única opción, como en el caso de una secuencia de columnas. En estas situaciones, al igual que en data.frame, puede escribir `DT[, 5:10]` y `DT[,c(1,4,10)]`. Sin embargo, es más robusto (ante futuros cambios en el número y orden de las columnas de sus datos) usar un rango con nombre como `DT[,columnRed:columnViolet]` o nombrar cada una `DT[,c("columnRed","columnOrange","columnYellow")]`. Al principio es más laborioso, pero probablemente se lo agradecerá a sí mismo y sus colegas podrían agradecérselo en el futuro. Al menos podrá decir que se esforzó al máximo por escribir código robusto si algo sale mal.
+
+Sin embargo, lo que realmente queremos que hagas es `DT[,.(columnRed,columnOrange,columnYellow)]`; es decir, usar los nombres de columna como si fueran variables directamente dentro de `DT[...]`. No tiene que prefijar cada columna con `DT$` como lo hace en data.frame. La parte `.()` es solo un alias para `list()` y puede usar `list()` en su lugar si lo prefiere. Puede colocar cualquier expresión R de nombres de columna, usando cualquier paquete R, que devuelva diferentes tipos de diferentes longitudes, justo ahí. Queríamos alentarle a hacer eso tan fuertemente en el pasado que deliberadamente no hicimos que `DT[,5]` funcionara en absoluto. Antes de v1.9.8 lanzado en noviembre de 2016, `DT[,5]` solía devolver solo `5`. La idea era que podíamos enseñar más simplemente un hecho de que las partes dentro de `DT[...]` se evalúan dentro del marco de DT siempre (ven los nombres de columna como si fueran variables). Y `5` evalúa a `5`, por lo que el comportamiento fue consistente con la regla única. Les solicitamos que superaran un obstáculo adicional deliberado `DT[,5,with=FALSE]` si realmente deseaban seleccionar una columna por nombre o número. A partir de noviembre de 2016, ya no es necesario usar `with=FALSE` y veremos cómo una mayor consistencia con data.frame en este sentido ayudará o perjudicará tanto a los usuarios nuevos como a los antiguos. Los nuevos usuarios que no lean estas preguntas frecuentes, ni siquiera esta primera entrada, esperamos que no se tropiecen tan pronto con data.table como antes si esperaban que funcionara como data.frame. Esperamos que no se pierdan nuestra intención y recomendación de colocar expresiones de columnas dentro de `DT[i, j, by]`. Si usan data.table como data.frame, no obtendrán ningún beneficio. Si conocen a alguien en esa situación, por favor, anímenle a leer este documento como ustedes.
+
+Recordatorio: puede colocar *cualquier* expresión de R dentro de `DT[...]` usando los nombres de columna como si fueran variables; por ejemplo, pruebe `DT[, colA*colB/2]`. Esto devuelve un vector porque usó los nombres de columna como si fueran variables. Envuélvalo en `.()` para devolver una data.table; es decir, `DT[,.(colA*colB/2)]`. Asígnele un nombre: `DT[,.(myResult = colA*colB/2)]`. Dejamos que adivine cómo devolver dos valores de esta consulta. También es bastante común realizar varias acciones dentro de un cuerpo anónimo: `DT[, { x<-colA+10; x*x/2 }]` o llamar a la función de otro paquete: `DT[ , fitdistr(columnA, "normal")]`.
+
+## ¿Por qué `DT[,"region"]` devuelve un data.table de 1 columna en lugar de un vector?
+
+Consulte la [respuesta anterior](#j-num). Prueba con `DT$region` o `DT[["region"]]`.
+
+## ¿Por qué `DT[, región]` devuelve un vector para la columna "región"? Me gustaría una tabla data.table de una sola columna.
+
+Pruebe `DT[ , .(region)]` en su lugar. `.()` es un alias para `list()` y garantiza que se devuelva una data.table.
+
+Continúe leyendo y consulte las preguntas frecuentes a continuación. Revise los documentos completos antes de atascarse en una parte.
+
+## ¿Por qué no funciona `DT[, x, y, z]`? Quería las tres columnas `x`, `y` y `z`.
+
+La expresión `j` es el segundo argumento. Pruebe `DT[ , c("x","y","z")]` o `DT[ , .(x,y,z)]`.
+
+## Asigné la variable `mycol="x"`, pero `DT[, mycol]` devuelve un error. ¿Cómo puedo hacer que busque el nombre de la columna contenida en la variable `mycol`?
+
+El error es que no se puede encontrar la columna denominada `"mycol"`, y este error es correcto. El alcance de `data.table` es diferente al de `data.frame` en el sentido de que puede usar los nombres de las columnas como si fueran variables directamente dentro de `DT[...]` sin anteponer `DT$` a cada nombre de columna; consulte la pregunta frecuente 1.1 anterior.
+
+Para usar `mycol` para seleccionar la columna `x` de `DT`, hay algunas opciones:
+
+```r
+DT[, ..mycol] # .. prefix conveys to look for the mycol one level up in calling scope
+DT[, mycol, with=FALSE] # revert to data.frame behavior
+DT[[mycol]] # treat DT as a list and use [[ from base R
+```
+
+Consulte `?data.table` para obtener más detalles sobre el prefijo `..`.
+
+El argumento `with` toma su nombre de la función `base` `with()`. Cuando `with=TRUE` (predeterminado), `data.table` funciona de forma similar a `with()`, es decir, `DT[, mycol]` se comporta como `with(DT, mycol)`. Cuando `with=FALSE`, las reglas de evaluación estándar de `data.frame` se aplican a todas las variables en `j` y ya no se pueden usar nombres de columna directamente.
+
+## ¿Cuáles son los beneficios de poder utilizar nombres de columnas como si fueran variables dentro de 'DT[...]'?
+
+`j` no tiene por qué ser solo nombres de columnas. Puede escribir cualquier *expresión* de R de nombres de columnas directamente en `j`, *p. ej.*, `DT[ , media(x*y/z)]`. Lo mismo aplica para `i`, *p. ej.*, `DT[x>1000, suma(y*z)]`.
+
+Esto ejecuta la expresión `j` en el conjunto de filas donde la expresión `i` es verdadera. Ni siquiera necesita devolver datos, *p. ej.*, `DT[x>1000, plot(y, z)]`. Puede hacer `j` por grupo simplemente agregando `by = `; p. ej., `DT[x>1000, sum(y*z), by = w]`. Esto ejecuta `j` para cada grupo en la columna `w` pero solo sobre las filas donde `x>1000`. Al colocar las 3 partes de la consulta (i=where, j=select y by=group by) dentro de los corchetes, data.table ve esta consulta como un todo antes de que se evalúe cualquier parte de ella. Por lo tanto, puede optimizar la consulta combinada para el rendimiento. Puede hacer esto porque el lenguaje R tiene una evaluación diferida única (Python y Julia no la tienen). data.table ve las expresiones dentro de `DT[...]` antes de que se evalúen y las optimiza antes de la evaluación. Por ejemplo, si data.table ve que solo está usando 2 columnas de 100, no se molestará en filtrar las 98 que no son necesarias para su expresión j.
+
+## Vale, empiezo a entender la función de data.table, pero ¿por qué no mejoraron `data.frame` en R? ¿Por qué tiene que ser un paquete nuevo?
+
+Como se [resaltó arriba](#j-num), `j` en `[.data.table` es fundamentalmente diferente de `j` en `[.data.frame`. Incluso si algo tan simple como `DF[ , 1]` se modificara en R base para devolver un data.frame en lugar de un vector, esto afectaría el código existente en miles de paquetes CRAN y código de usuario. En cuanto creamos una nueva clase que heredara de data.frame, tuvimos la oportunidad de cambiar algunas cosas, y lo hicimos. Queremos que data.table sea ligeramente diferente y funcione de esta manera para que funcione una sintaxis más compleja. También existen otras diferencias (véase [abajo](#SmallerDiffs)).
+
+Además, data.table hereda de `data.frame`. También es un `data.frame`. Un data.table se puede pasar a cualquier paquete que solo acepte `data.frame` y ese paquete puede usar la sintaxis `[.data.frame` en data.table. Consulta [esta respuesta](https://stackoverflow.com/a/10529888/403310) para saber cómo lograrlo.
+
+También *hemos* propuesto mejoras para R siempre que ha sido posible. Una de ellas se aceptó como nueva característica en R 2.12.0:
+
+> `unique()` y `match()` ahora son más rápidos en vectores de caracteres donde todos los elementos están en la caché global CHARSXP y tienen codificación sin marcar (ASCII). Gracias a Matt Dowle por sugerir mejoras en la generación del código hash en unique.c.
+
+Una segunda propuesta fue usar `memcpy` en duplicate.c, que es mucho más rápido que un bucle for en C. Esto mejoraría la *forma* en que R copia datos internamente (en algunas mediciones, hasta 13 veces). El hilo sobre r-devel está [aquí](https://stat.ethz.ch/pipermail/r-devel/2010-April/057249.html).
+
+Una tercera propuesta más significativa que fue aceptada es que R ahora usa el código de ordenamiento por radix de data.table a partir de R 3.3.0:
+
+> El algoritmo de ordenamiento por radio y su implementación de data.table (forder) reemplaza el ordenamiento por radio (conteo) anterior y añade un nuevo método para order(). Contribuido por Matt Dowle y Arun Srinivasan, el nuevo algoritmo admite vectores lógicos, enteros (incluso con valores grandes), reales y de caracteres. Supera a todos los demás métodos, pero presenta algunas desventajas (véase ?sort).
+
+Este fue un gran evento para nosotros y lo celebramos hasta el cansancio. (En realidad, no).
+
+## ¿Por qué los valores predeterminados son así? ¿Por qué funciona así?
+
+La respuesta simple es que el autor principal lo diseñó originalmente para su propio uso. Así lo quiso. Le parece una forma más natural y rápida de escribir código, que también se ejecuta con mayor rapidez.
+
+## ¿Esto no se hace ya con `with()` y `subset()` en `base`?
+
+Algunas de las características comentadas hasta ahora son válidas. El paquete se basa en la funcionalidad básica. Hace lo mismo, pero requiere menos código y se ejecuta mucho más rápido si se usa correctamente.
+
+## ¿Por qué «X[Y]» devuelve también todas las columnas de «Y»? ¿No debería devolver un subconjunto de «X»?
+
+Esto se modificó en la versión 1.5.3 (febrero de 2011). Desde entonces, `X[Y]` incluye las columnas no unidas de `Y`. Esta función se denomina *ámbito heredado de unión* porque no solo las columnas `X` están disponibles para la expresión `j`, sino también las columnas `Y`. La desventaja es que `X[Y]` es menos eficiente, ya que cada elemento de las columnas no unidas de `Y` se duplica para coincidir con el (probablemente elevado) número de filas en `X` que coinciden. Por lo tanto, recomendamos encarecidamente `X[Y, j]` en lugar de `X[Y]`. Consulte las [siguientes preguntas frecuentes](#MergeDiff).
+
+## ¿Cuál es la diferencia entre `X[Y]` y `merge(X, Y)`? {#MergeDiff}
+
+`X[Y]` es una unión (join) que busca las filas de `X` utilizando `Y` (o la clave de `Y` si tiene una) como índice.
+
+`Y[X]` es una unión que busca las filas de `Y` utilizando `X` (o la clave de `X` si tiene una) como índice.
+
+`merge(X,Y)`[^1] funciona en ambos sentidos simultáneamente. El número de filas de `X[Y]` e `Y[X]` suele ser diferente, mientras que el número de filas devuelto por `merge(X, Y)` y `merge(Y, X)` es el mismo.
+
+*PERO* eso pasa por alto el punto principal. La mayoría de las tareas requieren que se haga algo en los datos después de una unión o fusión. ¿Por qué fusionar todas las columnas de datos, solo para usar un pequeño subconjunto de ellas después? Puede sugerir `merge(X[ , ColsNeeded1], Y[ , ColsNeeded2])`, pero eso requiere que el programador determine qué columnas se necesitan. `X[Y, j]` en data.table hace todo eso en un solo paso para ti. Cuando escribes `X[Y, sum(foo*bar)]`, data.table inspecciona automáticamente la expresión `j` para ver qué columnas usa. Solo creará un subconjunto de esas columnas; las demás se ignoran. Solo se crea memoria para las columnas que usa `j` y las columnas `Y` disfrutan de las reglas de reciclaje estándar de R dentro del contexto de cada grupo. Digamos que `foo` está en `X` y `bar` está en `Y` (junto con otras 20 columnas en `Y`). ¿No es `X[Y, sum(foo*bar)]` más rápido de programar y más rápido de ejecutar que el desperdicio de un `merge` de todo seguido de `subset`?
+
+[^1]: Nos referimos al método `merge` para data.table o al método `merge` para `data.frame`, ya que ambos funcionan de la misma manera. Consulte `?merge.data.table` y [a continuación](#r-dispatch) para obtener más información sobre el envío de métodos.
+
+## ¿Algo más sobre `X[Y, sum(foo*bar)]`?
+
+Este comportamiento cambió en la v1.9.4 (septiembre de 2014). Ahora realiza la unión `X[Y]` y luego ejecuta `sum(foo*bar)` sobre todas las filas; es decir, `X[Y][ , sum(foo*bar)]`. Antes, ejecutaba `j` para cada *grupo* de `X` con el que coincidía cada fila de `Y`. Esto aún se puede hacer, ya que es muy útil, pero ahora es necesario especificar explícitamente `by = .EACHI`, es decir, `X[Y, sum(foo*bar), by = .EACHI]`. A esto lo llamamos *agrupación por cada `i`*.
+
+Por ejemplo, (para complicarlo aún más, también se utiliza *join legacy scope*):
+
+```{r}
+X = data.table(grp = c("a", "a", "b",
+ "b", "b", "c", "c"), foo = 1:7)
+setkey(X, grp)
+Y = data.table(c("b", "c"), bar = c(4, 2))
+X
+Y
+X[Y, sum(foo*bar)]
+X[Y, sum(foo*bar), by = .EACHI]
+```
+
+## Qué bien. ¿Cómo lograron cambiarlo si los usuarios dependían del comportamiento anterior?
+
+La solicitud de cambio provino de los usuarios. Se creía que, si una consulta realiza agrupaciones, debería incluirse un `by=` explícito para facilitar la lectura del código. Se proporcionó una opción para restablecer el comportamiento anterior: `options(datatable.old.bywithoutby)`, con `FALSE` por defecto. Esto permitió la actualización para probar las demás nuevas funciones y correcciones de errores de la v1.9.4, y la posterior migración de cualquier consulta `by-without-by` cuando estuviera lista, añadiéndoles `by=.EACHI`. Conservamos 47 pruebas previas al cambio y las reintrodujimos como nuevas pruebas, con `options(datatable.old.bywithoutby=TRUE)`. Añadimos un mensaje de inicio sobre el cambio y cómo volver al comportamiento anterior. Tras un año, la opción quedó obsoleta con una advertencia al usarla. Tras dos años, se eliminó la opción para volver al comportamiento anterior.
+
+De los 66 paquetes en CRAN o Bioconductor que dependían de o importaban data.table al momento de la publicación de la v1.9.4 (ahora son más de 300), solo uno se vio afectado por el cambio. Esto podría deberse a que muchos paquetes no cuentan con pruebas exhaustivas, o simplemente a que la agrupación por cada fila en `i` no se usaba mucho en los paquetes posteriores. Siempre probamos la nueva versión con todos los paquetes dependientes antes del lanzamiento y coordinamos cualquier cambio con los responsables. Por lo tanto, esta versión fue bastante sencilla en ese sentido.
+
+Otra razón convincente para realizar el cambio fue que, anteriormente, no existía una forma eficiente de lograr lo que `X[Y, sum(foo*bar)]` hace ahora. Se debía escribir `X[Y][ , sum(foo*bar)]`. Esto no era óptimo porque `X[Y]` unía todas las columnas y las pasaba a la segunda consulta compuesta sin saber que solo se necesitaban `foo` y `bar`. Para solucionar este problema de eficiencia, se requería un esfuerzo de programación adicional: `X[Y, list(foo, bar)][ , sum(foo*bar)]`. El cambio a `by = .EACHI` ha simplificado esto al permitir que ambas consultas se expresen dentro de una única consulta `DT[...]` para mayor eficiencia.
+
+# Sintaxis general
+
+## ¿Cómo puedo evitar escribir una expresión `j` muy larga? Dijiste que debería usar la columna *nombres*, pero tengo muchas columnas.
+
+Al agrupar, la expresión `j` puede usar nombres de columna como variables, como ya sabe, pero también puede usar el símbolo reservado `.SD`, que hace referencia al **S**subconjunto de **D**ata.table** para cada grupo (excluyendo las columnas de agrupación). Por lo tanto, para sumar todas las columnas, simplemente se usa `DT[ , lapply(.SD, sum), by = grp]`. Puede parecer complicado, pero es rápido de escribir y de ejecutar. Tenga en cuenta que no es necesario crear una función anónima. El objeto `.SD` se implementa internamente de forma eficiente y es más eficiente que pasar un argumento a una función. Sin embargo, si el símbolo `.SD` aparece en `j`, data.table debe rellenar `.SD` por completo para cada grupo, incluso si `j` no lo usa del todo.
+
+Por lo tanto, no utilice, por ejemplo, `DT[ , sum(.SD[["sales"]]), by = grp]`. Esto funciona, pero es ineficiente y poco elegante. `DT[ , sum(sales), by = grp]` es lo que se pretendía, y podría ser cientos de veces más rápido. Si utiliza *todos* los datos en `.SD` para cada grupo (como en `DT[ , lapply(.SD, sum), by = grp]`), es un uso muy adecuado de `.SD`. Si utiliza *varias* columnas, pero no *todas*, puede combinar `.SD` con `.SDcols`; consulte `?data.table`.
+
+## ¿Por qué el valor predeterminado para `mult` ahora es `"all"`?
+
+En la v1.5.3, el valor predeterminado se cambió a `"all"`. Cuando `i` (o la clave de `i`, si la tiene) tiene menos columnas que la clave de `x`, `mult` ya estaba configurado automáticamente como `"all"`. Cambiar el valor predeterminado facilita y aclara esto para los usuarios, ya que se presentaba con bastante frecuencia.
+
+En versiones anteriores a la v1.3, `"all"` era más lento. Internamente, `"all"` se implementaba uniendo con `"first"` y luego desde cero con `"last"`. Tras esto, se realizaba una comparación entre ellos para determinar el intervalo de coincidencias en `x` para cada fila en `i`. Sin embargo, la mayoría de las veces, unimos filas individuales, donde `"first"`, `"last"` y `"all"` devuelven el mismo resultado. Preferimos el máximo rendimiento en la mayoría de los casos, por lo que el valor predeterminado elegido fue `"first"`. Al trabajar con una clave no única (generalmente una sola columna que contiene una variable de agrupación), `DT["A"]` devolvía la primera fila de ese grupo, por lo que se necesitaba `DT["A", mult = "all"]` para devolver todas las filas de ese grupo.
+
+En la v1.4, la búsqueda binaria en C se modificó para que se ramificara en el nivel más profundo para encontrar el primero y el último. Es probable que esta ramificación ocurra dentro de las mismas páginas finales de RAM, por lo que ya no debería haber una desventaja de velocidad al establecer `mult` como `"all"` por defecto. Advertimos que el valor predeterminado podría cambiar e implementamos el cambio en la v1.5.3.
+
+Una versión futura de data.table podría permitir distinguir entre una clave y una *clave única*. Internamente, `mult = "all"` funcionaría de forma similar a `mult = "first"` cuando todas las columnas de la clave de `x` estuvieran unidas y la clave de `x` fuera única. data.table necesitaría comprobaciones al insertar y actualizar para garantizar que se mantenga una clave única. Una ventaja de especificar una clave única sería que, además de mejorar el rendimiento, data.table garantizaría que no se insertaran duplicados.
+
+## Estoy usando `c()` en `j` y obtengo resultados extraños.
+
+Esta es una fuente común de confusión. En `data.frame` se suele usar, por ejemplo:
+
+```{r}
+DF = data.frame(x = 1:3, y = 4:6, z = 7:9)
+DF
+DF[ , c("y", "z")]
+```
+
+Que devuelve las dos columnas. En data.table, sabe que puede usar los nombres de las columnas directamente y podría intentar:
+
+```{r}
+DT = data.table(DF)
+DT[ , c(y, z)]
+```
+
+Pero esto devuelve un vector. Recuerde que la expresión `j` se evalúa en el entorno de `DT` y `c()` devuelve un vector. Si se requieren dos o más columnas, utilice `list()` o `.()` en su lugar:
+
+```{r}
+DT[ , .(y, z)]
+```
+
+`c()` también puede ser útil en una data.table, pero su comportamiento es diferente al de `[.data.frame`.
+
+## He creado una tabla compleja con muchas columnas. Quiero usarla como plantilla para una nueva tabla; es decir, crear una tabla sin filas, pero con los nombres y tipos de columna copiados de mi tabla. ¿Es fácil hacerlo?
+
+Sí. Si su tabla compleja se llama `DT`, intente `NEWDT = DT[0]`.
+
+## ¿Es un data.table nulo lo mismo que `DT[0]`?
+
+No. Por "data.table nulo" nos referimos al resultado de `data.table(NULL)` o `as.data.table(NULL)`; *es decir*,
+
+```{r}
+data.table(NULL)
+data.frame(NULL)
+as.data.table(NULL)
+as.data.frame(NULL)
+is.null(data.table(NULL))
+is.null(data.frame(NULL))
+```
+
+El objeto data.table|`frame` nulo es `NULL` con algunos atributos adjuntos, lo que significa que ya no es `NULL`. En R, solo `NULL` puro es `NULL`, como se prueba con `is.null()`. Al referirnos al objeto "data.table" nulo, usamos `null` en minúscula para distinguirlo de `NULL` en mayúscula. Para comprobar si el objeto data.table es nulo, use `length(DT) == 0` o `ncol(DT) == 0` (`length` es ligeramente más rápido, ya que es una función primitiva).
+
+Una data.table *vacía* (`DT[0]`) tiene una o más columnas, todas ellas vacías. Estas columnas vacías aún conservan nombres y tipos.
+
+```{r}
+DT = data.table(a = 1:3, b = c(4, 5, 6), d = c(7L,8L,9L))
+DT[0]
+sapply(DT[0], class)
+```
+
+## ¿Por qué se ha eliminado el alias `DT()`? {#DTremove1}
+
+`DT` se introdujo originalmente como contenedor para una lista de expresiones `j`. Dado que `DT` era un alias de data.table, era una forma práctica de gestionar el reciclaje silencioso en casos en que cada elemento de la lista `j` evaluaba con longitudes diferentes. Sin embargo, el alias era una de las razones por las que la agrupación era lenta.
+
+A partir de la v1.3, se deben pasar `list()` o `.()` al argumento `j`. Esto es mucho más rápido, especialmente cuando hay muchos grupos. Internamente, este cambio no fue trivial. El reciclaje de vectores ahora se realiza internamente, junto con otras mejoras de velocidad para la agrupación.
+
+## Pero mi código usa `j = DT(...)` y funciona. Las preguntas frecuentes anteriores indican que se ha eliminado `DT()`. {#DTremove2}
+
+Entonces estás usando una versión anterior a la 1.5.3. Antes de la 1.5.3, `[.data.table` detectaba el uso de `DT()` en `j` y lo reemplazaba automáticamente con una llamada a `list()`. Esto facilitaba la transición para los usuarios existentes.
+
+## ¿Cuáles son las reglas de alcance para las expresiones 'j'?
+
+Piense en el subconjunto como un entorno donde todos los nombres de columna son variables. Cuando se utiliza la variable `foo` en la `j` de una consulta como `X[Y, sum(foo)]`, se busca `foo` en el siguiente orden:
+
+ 1. El alcance del subconjunto de `X`; *es decir*, los nombres de las columnas de `X`.
+ 2. El alcance de cada fila de `Y`; *es decir*, los nombres de las columnas de `Y` (*unir el alcance heredado*)
+ 3. El alcance del marco de llamada; *por ejemplo*, la línea que aparece antes de la consulta data.table.
+ 4. Ejercicio para el lector: ¿luego navega los marcos de llamada o va directamente a `globalenv()`?
+ 5. El entorno global
+
+Esto es *alcance léxico*, como se explica en [R FAQ 3.3.1](https://cran.r-project.org/doc/FAQ/R-FAQ.html#Lexical-scoping). Sin embargo, el entorno en el que se creó la función no es relevante, ya que *no hay función*. No se pasa ninguna *función* anónima a `j`. En su lugar, se pasa un *cuerpo* anónimo a `j`; por ejemplo,
+
+```{r}
+DT = data.table(x = rep(c("a", "b"), c(2, 3)), y = 1:5)
+DT
+DT[ , {z = sum(y); z + 3}, by = x]
+```
+
+Algunos lenguajes de programación llaman a esto un *lambda*.
+
+## ¿Puedo rastrear la expresión 'j' a medida que se ejecuta a través de los grupos? {#j-trace}
+
+Pruebe algo como esto:
+
+```{r}
+DT[ , {
+ cat("Objects:", paste(objects(), collapse = ","), "\n")
+ cat("Trace: x=", as.character(x), " y=", y, "\n")
+ sum(y)},
+ by = x]
+```
+
+## Dentro de cada grupo, ¿por qué las variables del grupo tienen una longitud de 1?
+
+[Arriba](#j-trace), `x` es una variable de agrupación y (a partir de la v1.6.1) tiene `length` 1 (si se inspecciona o se usa en `j`). Esto se hace por eficiencia y conveniencia. Por lo tanto, no hay diferencia entre las dos siguientes afirmaciones:
+
+```{r}
+DT[ , .(g = 1, h = 2, i = 3, j = 4, repeatgroupname = x, sum(y)), by = x]
+DT[ , .(g = 1, h = 2, i = 3, j = 4, repeatgroupname = x[1], sum(y)), by = x]
+```
+
+Si necesita el tamaño del grupo actual, utilice `.N` en lugar de llamar a `length()` en cualquier columna.
+
+## Solo se imprimen las primeras 10 filas, ¿cómo imprimo más?
+
+Aquí ocurren dos cosas. Primero, si el número de filas en una tabla data.table es grande (`> 100` por defecto), se imprime un resumen de la tabla en la consola por defecto. Segundo, el resumen de una tabla data.table grande se imprime tomando las `n` filas superiores e inferiores (`= 5` por defecto) de la tabla data.table e imprimiendo solo esas. Ambos parámetros (cuándo activar un resumen y qué parte de la tabla usar como resumen) se pueden configurar mediante el mecanismo `options` de R o llamando directamente a la función `print`.
+
+Por ejemplo, para que el resumen de una tabla data.table solo se realice cuando esta tenga más de 50 filas, podría usar `options(datatable.print.nrows = 50)`. Para deshabilitar el resumen predeterminado por completo, podría usar `options(datatable.print.nrows = Inf)`. También podría llamar a `print` directamente, como en `print(your.data.table, nrows = Inf)`.
+
+Si desea mostrar más de las 10 filas superiores (e inferiores) de un resumen de data.table (digamos que prefiere 20), configure `options(datatable.print.topn = 20)`, por ejemplo. También puede llamar a `print` directamente, como en `print(your.data.table, topn = 20)`.
+
+## Con una unión `X[Y]`, ¿qué pasa si `X` contiene una columna llamada `"Y"`?
+
+Cuando `i` es un nombre único, como `Y`, se evalúa en el marco de llamada. En todos los demás casos, como las llamadas a `.()` u otras expresiones, `i` se evalúa dentro del ámbito de `X`. Esto facilita las autouniones sencillas, como `X[J(unique(colA)), mult = "first"]`.
+
+## `X[Z[Y]]` falla porque `X` contiene una columna `"Y"`. Me gustaría usar la tabla `Y` en el ámbito de llamada.
+
+La parte `Z[Y]` no es un nombre único, por lo que se evalúa dentro del marco de `X` y surge el problema. Pruebe `tmp = Z[Y]; X[tmp]`. Esto es robusto para `X` que contiene una columna `"tmp"`, ya que `tmp` es un nombre único. Si se encuentran conflictos de este tipo con frecuencia, una solución sencilla podría ser nombrar todas las tablas en mayúsculas y todos los nombres de las columnas en minúsculas, o algún esquema similar.
+
+## ¿Puedes explicar con más detalle por qué data.table está inspirado en la sintaxis 'A[B]' en 'base'?
+
+Considere la sintaxis `A[B]` usando una matriz de ejemplo `A`:
+
+```{r}
+A = matrix(1:12, nrow = 4)
+A
+```
+
+Para obtener las celdas `(1, 2) = 5` y `(3, 3) = 11` muchos usuarios (creemos) pueden intentar esto primero:
+
+```{r}
+A[c(1, 3), c(2, 3)]
+```
+
+Sin embargo, esto devuelve la unión de esas filas y columnas. Para referenciar las celdas, se requiere una matriz de dos columnas. `?Extract` dice:
+
+> Al indexar matrices mediante `[`, un único argumento `i` puede ser una matriz con tantas columnas como dimensiones de `x`; el resultado es entonces un vector con elementos correspondientes a los conjuntos de índices en cada fila de `i`.
+
+Vamos a intentarlo de nuevo.
+
+```{r}
+B = cbind(c(1, 3), c(2, 3))
+B
+A[B]
+```
+
+Una matriz es una estructura bidimensional con nombres de filas y columnas. ¿Podemos hacer lo mismo con los nombres?
+
+```{r}
+rownames(A) = letters[1:4]
+colnames(A) = LETTERS[1:3]
+A
+B = cbind(c("a", "c"), c("B", "C"))
+A[B]
+```
+
+Sí, podemos. ¿Podemos hacer lo mismo con un data.frame?
+
+```{r}
+A = data.frame(A = 1:4, B = letters[11:14], C = pi*1:4)
+rownames(A) = letters[1:4]
+A
+B
+A[B]
+```
+
+Pero, observe que el resultado se convirtió a `character`. R convirtió `A` a `matrix` primero para que la sintaxis funcionara, pero el resultado no es ideal. Intentemos convertir `B` en `data.frame`.
+
+```{r}
+B = data.frame(c("a", "c"), c("B", "C"))
+cat(try(A[B], silent = TRUE))
+```
+
+Por lo tanto, no podemos filtrar un `data.frame` con otro `data.frame` en R base. ¿Qué sucede si queremos nombres de fila y de columna que no sean `character`, sino `entero` o `float`? ¿Qué sucede si queremos más de dos dimensiones de tipos mixtos? Introduzcamos data.table.
+
+Más aún, las matrices, en especial las dispersas, suelen almacenarse en una tupla de tres columnas: `(i, j, valor)`. Esto puede considerarse como un par clave-valor donde `i` y `j` forman una clave de dos columnas. Si tenemos más de un valor, quizás de diferentes tipos, podría ser `(i, j, val1, val2, val3, ...)`. Esto se parece mucho a un `data.frame`. Por lo tanto, data.table extiende `data.frame`, de modo que un `data.frame` `X` puede ser «filtrado» por un `data.frame` `Y`, lo que da lugar a la sintaxis `X[Y]`.
+
+## ¿Se puede cambiar R base para que haga esto, en lugar de un nuevo paquete?
+
+`data.frame` se usa *en todas partes*, por lo que es muy difícil modificarlo. data.table *hereda* de `data.frame`. También *es* un `data.frame`. Un data.table *puede* pasarse a cualquier paquete que *solo* acepte `data.frame`. Cuando ese paquete usa la sintaxis `[.data.frame` en el data.table, funciona. Esto se debe a que `[.data.table` verifica desde dónde se llamó. Si se llamó desde dicho paquete, `[.data.table` desvía a `[.data.frame`.
+
+## He oído que la sintaxis data.table es análoga a SQL.
+
+Sí:
+
+ - `i` $\Leftrightarrow$ donde
+ - `j` $\Leftrightarrow$ select
+ - `:=` $\Leftrightarrow$ actualizar
+ - `por` $\Leftrightarrow$ group by
+ - `i` $\Leftrightarrow$ order by (en sintaxis compuesta)
+ - `i` $\Leftrightarrow$ having (en sintaxis compuesta)
+ - `nomatch = NA` $\Leftrightarrow$ outer join
+ - `nomatch = NULL` $\Leftrightarrow$ inner join
+ - `mult = "first"|"last"` $\Leftrightarrow$ N/A porque SQL es inherentemente desordenado
+ - `roll = TRUE` $\Leftrightarrow$ N/A porque SQL es inherentemente desordenado
+
+La forma general es:
+
+```r
+DT[where, select|update, group by][order by][...] ... [...]
+```
+
+Una ventaja clave de los vectores columna en R es que están *ordenados*, a diferencia de SQL[^2]. Podemos usar funciones ordenadas en consultas `data.table`, como `diff()`, y cualquier función de R de cualquier paquete, no solo las definidas en SQL. Una desventaja es que los objetos de R deben caber en memoria; sin embargo, con varios paquetes de R como `ff`, `bigmemory`, `mmap` e `indexing`, esto está cambiando.
+
+[^2]: Puede resultar sorprendente saber que `select top 10 * from ...` no devuelve las mismas filas de forma fiable a lo largo del tiempo en SQL. Es necesario incluir una cláusula `order by` o usar un índice agrupado para garantizar el orden de las filas; es decir, SQL es inherentemente desordenado.
+
+## ¿Cuáles son las diferencias de sintaxis más pequeñas entre `data.frame` y data.table {#SmallerDiffs}?
+
+ - `DT[3]` se refiere a la 3ra *fila*, pero `DF[3]` se refiere a la 3ra *columna*
+ - `DT[3, ] == DT[3]`, pero `DF[ , 3] == DF[3]` (de manera algo confusa en data.frame, mientras que data.table es consistente)
+ - Por esta razón decimos que la coma es *opcional* en `DT`, pero no opcional en `DF`
+ - `DT[[3]] == DF[, 3] == DF[[3]]`
+ - `DT[i, ]`, donde `i` es un entero único, devuelve una sola fila, al igual que `DF[i, ]`, pero a diferencia de un filtro de de matriz de una sola fila, que devuelve un vector.
+ - `DT[ , j]` donde `j` es un entero único, devuelve un data.table de una columna, a diferencia de `DF[, j]` que devuelve un vector de manera predeterminada
+ - `DT[ , "colA"][[1]] == DF[ , "colA"]`.
+ - `DT[ , colA] == DF[ , "colA"]` (actualmente en data.table v1.9.8 pero está a punto de cambiar, consulte las notas de la versión)
+ - `DT[ , list(colA)] == DF[ , "colA", drop = FALSE]`
+ - `DT[NA]` devuelve 1 fila de `NA`, pero `DF[NA]` devuelve una copia completa de `DF` que contiene `NA` en todas partes. El símbolo «NA» es de tipo «lógico» en R y, por lo tanto, se recicla mediante «[.data.frame». La intención del usuario probablemente era «DF[NA_integer_]». `[.data.table` desvía a esta probable intención automáticamente, por conveniencia.
+ - `DT[c(TRUE, NA, FALSE)]` trata el `NA` como `FALSE`, pero `DF[c(TRUE, NA, FALSE)]` devuelve `NA` filas para cada `NA` - `DT[ColA == ColB]` es más simple que `DF[!is.na(ColA) & !is.na(ColB) & ColA == ColB, ]`
+ - `data.frame(list(1:2, "k", 1:4))` crea 3 columnas, data.table crea una columna `list`.
+ - `check.names` es por defecto `TRUE` en `data.frame` pero `FALSE` en data.table, por conveniencia.
+ - `data.table` siempre ha establecido `stringsAsFactors=FALSE` por defecto. En R 4.0.0 (abril de 2020), el valor predeterminado de `data.frame` se cambió de `TRUE` a `FALSE` y ya no existe diferencia en este aspecto. - Los vectores atómicos en las columnas `list` se contraen cuando se imprimen usando `", "` en `data.frame`, pero `","` en data.table con una coma final después del sexto elemento para evitar la impresión accidental de objetos incrustados grandes.
+ - A diferencia de data.frames, un data.table no puede almacenar filas sin columnas, ya que las filas se consideran hijas de las columnas: `nrow(DF[, 0])` devuelve el número de filas, mientras que `nrow(DT[, 0])` siempre devuelve 0; pero vea el problema [#2422](https://github.com/Rdatatable/data.table/issues/2422).
+
+En `[.data.frame`, solemos configurar `drop = FALSE`. Si nos olvidamos, pueden surgir errores en casos extremos donde se seleccionan columnas individuales y, de repente, se devuelve un vector en lugar de una sola columna `data.frame`. En `[.data.table`, aprovechamos la oportunidad para hacerlo consistente y eliminamos `drop`.
+
+Cuando se pasa un data.table a un paquete que no lo tiene en cuenta, ese paquete no se preocupa por ninguna de estas diferencias; simplemente funciona.
+
+## Estoy usando `j` solo por su efecto secundario, pero sigo obteniendo datos. ¿Cómo puedo detenerlo?
+
+En este caso, `j` se puede envolver con `invisible()`; por ejemplo, `DT[ , invisible(hist(colB)), by = colA]`[^3]
+
+[^3]: *por ejemplo*, `hist()` devuelve los puntos de interrupción además de trazar en el dispositivo gráfico.
+
+## ¿Por qué `[.data.table` ahora tiene un argumento `drop` desde v1.5?
+
+Para que data.table pueda heredar de `data.frame` sin usar `...`. Si usáramos `...`, no se detectarían los nombres de argumentos no válidos.
+
+El argumento `drop` nunca se utiliza en `[.data.table`. Es un marcador de posición para paquetes que no son compatibles con data.table cuando usan la sintaxis `[.data.frame` directamente en un data.table.
+
+## ¡Las uniones continuas son geniales y rapidísimas! ¿Fue difícil programarlas?
+
+La fila que prevalece en o antes de la fila `i` es la última fila que la búsqueda binaria prueba. Por lo tanto, `roll = TRUE` es básicamente un cambio en el código C de búsqueda binaria para devolver esa fila.
+
+## ¿Por qué `DT[i, col := value]` devuelve `DT` completo? Esperaba que no hubiera ningún valor visible (consistente con `<-`), o un mensaje o valor de retorno que indicara cuántas filas se actualizaron. No es evidente que los datos se hayan actualizado por referencia.
+
+Esto ha cambiado en la v1.8.3 para cumplir con sus expectativas. Actualice.
+
+Se devuelve la totalidad de `DT` (ahora de forma invisible) para que la sintaxis compuesta funcione; p. ej., `DT[i, done := TRUE][ , sum(done)]`. El número de filas actualizadas se devuelve cuando `verbose` es `TRUE`, ya sea por consulta o globalmente mediante `options(datatable.verbose = TRUE)`.
+
+## Bien, gracias. ¿Qué tenía de difícil que el resultado de `DT[i, col := valor]` se devolviera de forma invisible?
+
+R activa internamente la visibilidad para `[`. El valor de la columna eval de FunTab (ver [src/main/names.c](https://github.com/wch/r-source/blob/trunk/src/main/names.c)) para `[` es `0`, lo que significa que se activa `R_Visible` (ver [R-Internals sección 1.6](https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Autoprinting)). Por lo tanto, al intentar `invisible()` o configurar `R_Visible` a `0` directamente, `eval` en [src/main/eval.c](https://github.com/wch/r-source/blob/trunk/src/main/eval.c) lo activaba de nuevo.
+
+Para solucionar este problema, la clave fue dejar de intentar detener la ejecución del método de impresión después de un `:=`. En su lugar, dentro de `:=` ahora (a partir de la v1.8.3) configuramos un indicador global que el método de impresión usa para determinar si imprimir o no.
+
+## ¿Por qué a veces tengo que escribir 'DT' dos veces después de usar ':=' para imprimir el resultado en la consola?
+
+Esta es una desventaja desafortunada para que [#869](https://github.com/Rdatatable/data.table/issues/869) funcione. Si se usa un `:=` dentro de una función sin `DT[]` antes del final de la función, la próxima vez que se escriba `DT` en el prompt, no se imprimirá nada. Un `DT` repetido se imprimirá. Para evitar esto: incluya un `DT[]` después del último `:=` en su función. Si eso no es posible (por ejemplo, no es una función que pueda cambiar), se garantiza que `print(DT)` y `DT[]` en el prompt se imprimirán. Como antes, agregar un `[]` adicional al final de la consulta `:=` es un modismo recomendado para actualizar y luego imprimir; por ejemplo, `DT[,foo:=3L][]`.
+
+## He observado que `base::cbind.data.frame` (y `base::rbind.data.frame`) parecen ser modificados por data.table. ¿Cómo es posible? ¿Por qué?
+
+Era una solución temporal de último recurso antes de que se corrigiera la resolución de métodos S3 de rbind y cbind en R >= 4.0.0. En esencia, el problema residía en que `data.table` hereda de `data.frame`, *y* `base::cbind` y `base::rbind` (de forma única) realizan su propia resolución S3 internamente, como se documenta en `?cbind`. La solución alternativa para `data.table` consistía en añadir un bucle `for` al inicio de cada función directamente en `base`. Esta modificación se realizaba dinámicamente; es decir, se obtuvo la definición `base` de `cbind.data.frame`, se añadía el bucle `for` al inicio y luego se volvía a asignar a `base`. Esta solución se diseñó para ser robusta ante varias definiciones de `base::cbind.data.frame` en diferentes versiones de R, incluyendo cambios futuros desconocidos. Funcionó correctamente. Los requisitos en conflicto eran:
+
+ - `cbind(DT, DF)` debe funcionar. La definición de `cbind.data.table` no funcionaba porque `base::cbind` realizaba su propia resolución S3 y requería (antes de R 4.0.0) que el *primer* método `cbind` para cada objeto que se le pasa fuera *idéntico*. Esto no se cumple en `cbind(DT, DF)`, ya que el primer método para `DT` es `cbind.data.table`, pero el primer método para `DF` es `cbind.data.frame`. `base::cbind` entonces fallaba en su código interno `bind`, que parece tratar `DT` como una `lista` normal y devuelve una salida `matrix` de aspecto extraño e inutilizable. Véase [a continuación](#cbinderror). No podemos simplemente aconsejar a los usuarios que no llamen a `cbind(DT, DF)` porque paquetes como `ggplot2` hacen dicha llamada ([prueba 167.2](https://github.com/Rdatatable/data.table/blob/master/inst/tests/tests.Rraw#L444-L447)).
+
+ - Esto, naturalmente, llevó a intentar enmascarar `cbind.data.frame`. Dado que un data.table es un `data.frame`, `cbind` encontraría el mismo método para `DT` y `DF`. Sin embargo, esto tampoco funcionó porque `base::cbind` parece encontrar primero los métodos en `base`; *es decir*, `base::cbind.data.frame` no es enmascarable.
+
+ - Finalmente, intentamos enmascarar `cbind` (v1.6.5 y v1.6.6). Esto permitió que `cbind(DT, DF)` funcionara, pero introdujo problemas de compatibilidad con el paquete `IRanges`, ya que `IRanges` también enmascara `cbind`. Funcionaba si `IRanges` estaba en una posición inferior a data.table en la ruta `search()`, pero si `IRanges` estaba en una posición superior a data.table, `cbind` nunca se llamaría y la salida de `matrix`, de aspecto extraño, volvía a aparecer (ver [abajo](#cbinderror)).
+
+Muchas gracias al equipo central de R por solucionar el problema en septiembre de 2019. data.table v1.12.6+ ya no aplica la solución alternativa en R >= 4.0.0.
+
+## He leído sobre la resolución de métodos (p. ej., "merge" puede o no derivar a "merge.data.table"), pero ¿cómo sabe R cómo derivar? ¿Son los puntos significativos o especiales? ¿Cómo sabe R a qué función resolver y cuándo? {#r-dispatch}
+
+Esto se menciona con frecuencia, pero es increíblemente simple. Una función como `merge` es *genérica* si consiste en una llamada a `UseMethod`. Cuando se habla de si las funciones son o no *genéricas*, simplemente se escribe la función sin `()` después, se revisa el código del programa que contiene y si ven una llamada a `UseMethod`, entonces es *genérica*. ¿Qué hace `UseMethod`? Literalmente, combina el nombre de la función con la clase del primer argumento, separados por un punto (`.`) y luego llama a esa función, pasando los mismos argumentos. Así de simple. Por ejemplo, `merge(X, Y)` contiene una llamada a `UseMethod`, lo que significa que luego *despacha* (es decir, llama) a `paste("merge", class(X), sep = ".")`. Las funciones con puntos en su nombre pueden ser métodos o no. El punto es irrelevante, salvo que sea el separador que usa `UseMethod`. Conocer estos antecedentes debería ayudar a comprender por qué, por ejemplo, es obvio para los usuarios de R que `as.data.table.data.frame` es el método `data.frame` de la función genérica `as.data.table`. Además, puede ser útil aclarar que, sí, tienes razón, su nombre no indica que `ls.fit` no sea el método de ajuste de la función genérica `ls`. Solo se sabe escribiendo `ls` (no `ls()`) y observando que no se trata de una sola llamada a `UseMethod`.
+
+Quizás te preguntes: ¿dónde está documentado esto en R? Respuesta: Está bastante claro, pero primero debe saber que debe buscar en `?UseMethod` y *ese* archivo de ayuda contiene:
+
+> Cuando se aplica una función que llama a `UseMethod('fun')` a un objeto con el atributo de clase `c('first', 'second')`, el sistema busca una función llamada `fun.first` y, si la encuentra, la aplica al objeto. Si no se encuentra dicha función, se prueba con una función llamada `fun.second`. Si ningún nombre de clase produce una función adecuada, se utiliza la función `fun.default`, si existe, o se produce un error.
+
+Afortunadamente, una búsqueda en internet de "How does R method dispatch work" (N. de T.:"¿Cómo funciona el despacho de métodos en R?"), al momento de escribir esto muestra la página de ayuda "UseMethod" entre los primeros enlaces. Es cierto que otros enlaces profundizan rápidamente en las complejidades de S3 vs. S4, genéricos internos, etc.
+
+Sin embargo, características como el despacho básico de S3 (pegar el nombre de la función junto con el de la clase) son la razón por la que a algunos usuarios de R les encanta. Es muy simple. No se requieren registros ni firmas complejas. No hay mucho que aprender. Para crear el método `merge` para data.table, literalmente, solo se necesitó crear una función llamada `merge.data.table`.
+
+## ¿Por qué `T` y `F` se comportan de manera diferente a `TRUE` y `FALSE` en algunas consultas `data.table`?
+
+Usar `T` y `F` como abreviaturas de `TRUE` y `FALSE` en `data.table` puede provocar un comportamiento inesperado. Esto se debe a que `T` y `F` son variables globales redefinibles, lo que hace que se traten como nombres de variable en lugar de constantes lógicas. Este problema no ocurre con `TRUE` y `FALSE`. Se recomienda evitar `T` y `F` para usar R en general, pero se presenta en `data.table` de maneras sorprendentes, por ejemplo:
+
+```r
+DT <- data.table(x=rep(c("a", "b", "c"), each = 3), y=c(1, 3, 6), v=1:9)
+
+# Using TRUE/FALSE works as expected in cases like the ones below:
+
+DT[, .SD, .SDcols=c(TRUE, TRUE, FALSE)]
+# A) This selects the first two columns (x and y) and excludes the third one (v). Output:
+#> x y
+#> 1: a 1
+#> 2: a 3
+#> 3: a 6
+#> 4: b 1
+#> 5: b 3
+#> 6: b 6
+#> 7: c 1
+#> 8: c 3
+#> 9: c 6
+
+DT[, .SD, .SDcols=c(T, T, F), with=FALSE]
+# B) This forces data.table to treat T/F as logical constants.
+# Same output as DT[, .SD, .SDcols=c(TRUE, TRUE, FALSE)]
+
+# But, using T/F may lead to unexpected behavior in cases like:
+
+DT[, .SD, .SDcols=c(T, T, F)]
+# data.table treats T and F as variable names here, not logical constants. Output:
+#> Detected that j uses these columns:
+#> [1] TRUE TRUE FALSE
+```
+
+Como consejo general, `lintr::T_and_F_symbol_linter()` detecta el uso de `T` y `F` y sugiere reemplazarlos con `TRUE` y `FALSE` para evitar tales problemas.
+
+# Preguntas relacionadas con el tiempo de cómputo
+
+## Tengo 20 columnas y muchas filas. ¿Por qué una expresión de una sola columna es tan rápida?
+
+Varias razones:
+
+ - Solo se agrupa esa columna; las otras 19 se ignoran porque data.table inspecciona la expresión `j` y detecta que no utiliza las demás columnas.
+ - Se realiza una asignación de memoria solo para el grupo más grande, y luego esa memoria se reutiliza para los demás grupos. Hay muy poca basura que recolectar.
+ - R es un almacén de columnas en memoria; es decir, las columnas son contiguas en la RAM. Se minimiza la recuperación de páginas de la RAM a la caché L2.
+
+## No tengo una clave en una tabla grande, pero aun así la agrupación es muy rápida. ¿Por qué?
+
+data.table utiliza ordenamiento *radix*. Esto es significativamente más rápido que otros algoritmos de ordenamiento. Para más información, consulte [nuestras presentaciones](https://github.com/Rdatatable/data.table/wiki/Presentations), en particular las de useR!2015 Dinamarca.
+
+Esta es también una razón por la que `setkey()` es rápido.
+
+Cuando no se establece ninguna `clave`, o agrupamos en un orden diferente al de la clave, lo llamamos un `por` *ad hoc*.
+
+## ¿Por qué la agrupación por columnas en la clave es más rápida que un `por` *ad hoc*?
+
+Debido a que cada grupo es contiguo en RAM, se minimizan las búsquedas de páginas y la memoria se puede copiar en masa (`memcpy` en C) en lugar de realizar un bucle en C.
+
+## ¿Qué son los índices primarios y secundarios en data.table?
+
+Manual: [`?setkey`](https://www.rdocumentation.org/packages/data.table/functions/setkey) SO: [¿Cuál es el propósito de establecer una clave en data.table?](https://stackoverflow.com/questions/20039335/what-is-the-purpose-of-setting-a-key-in-data-table/20057411#20057411)
+
+`setkey(DT, col1, col2)` ordena las filas por la columna `col1` y, dentro de cada grupo de `col1`, las ordena por `col2`. Este es un *índice primario*. El orden de las filas se modifica *por referencia* en la RAM. Las uniones y agrupaciones posteriores en esas columnas clave aprovechan el orden de ordenación para mayor eficiencia. (Imagine lo difícil que sería buscar un número de teléfono en una guía telefónica impresa si no estuviera ordenado por apellido y nombre. Eso es literalmente todo lo que hace `setkey`: ordena las filas por las columnas que especifique). El índice no utiliza RAM. Simplemente modifica el orden de las filas en la RAM y marca las columnas clave. Análogo a un *índice agrupado* en SQL.
+
+Sin embargo, solo puede tener una clave principal porque los datos solo se pueden ordenar físicamente en RAM de una manera a la vez. Elija el índice principal para que sea el que use con más frecuencia (por ejemplo, `[id,date]`). A veces no hay una elección obvia para la clave principal o necesita unir y agrupar muchas columnas diferentes en diferentes órdenes. Ingrese un índice secundario. Esto usa memoria (`4*nrow` bytes independientemente del número de columnas en el índice) para almacenar el orden de las filas por las columnas que especifique, pero en realidad no reordena las filas en RAM. Las uniones y grupos posteriores aprovechan el orden de la clave secundaria, pero necesitan *saltar* a través de ese índice, por lo que no son tan eficientes como los índices primarios. Pero aún así, mucho más rápido que un escaneo vectorial completo. No hay límite para el número de índices secundarios, ya que cada uno es solo un vector de ordenación diferente. Normalmente no necesita crear índices secundarios. Se crean automáticamente y se usan automáticamente para usted usando data.table normalmente; *p. ej.* `DT[someCol == someVal, ]` y `DT[someCol %in% someVals, ]` crearán, adjuntarán y usarán el índice secundario. Esto es más rápido en data.table que un escaneo vectorial, por lo que la indexación automática está activada por defecto, ya que no hay penalización inicial. Existe una opción para desactivar la indexación automática; *p. ej.*, si se crean muchos índices e incluso la relativamente pequeña cantidad de memoria adicional resulta excesiva.
+
+Usamos las palabras *índice* y *clave* indistintamente.
+
+# Mensajes de error
+
+## "No se pudo encontrar la función `DT`"
+
+Ver arriba [aquí](#DTremove1) y [aquí](#DTremove2).
+
+## "argumentos no utilizados (`MySum = sum(v)`)"
+
+Este error es generado por `DT[ , MySum = sum(v)]`. Se pretendía `DT[ , .(MySum = sum(v))]`, o `DT[ , j = .(MySum = sum(v))]`.
+
+## "`translateCharUTF8` debe llamarse en un `CHARSXP`"
+
+Este error (y otros similares, por ejemplo, "`getCharCE` debe llamarse en un `CHARSXP`") podría no tener nada que ver con los datos de caracteres ni con la configuración regional. En realidad, podría ser un síntoma de una corrupción de memoria previa. Hasta la fecha, estos errores se han podido reproducir y se han solucionado rápidamente. Por favor, repórtelo a nuestro [rastreador de problemas](https://github.com/Rdatatable/data.table/issues).
+
+## `cbind(DT, DF)` devuelve un formato extraño, *por ejemplo* `Integer,5` {#cbinderror}
+
+Esto también ocurre antes de la v1.6.5, con `rbind(DT, DF)`. Actualice a la v1.6.7 o posterior.
+
+## "no se puede cambiar el valor del enlace bloqueado para `.SD`"
+
+`.SD` está bloqueado por diseño. Consulte `?data.table`. Si desea manipular `.SD` antes de usarlo o devolverlo, y no desea modificar `DT` con `:=`, primero haga una copia (consulte `?copy`), por ejemplo:
+
+```{r}
+DT = data.table(a = rep(1:3, 1:3), b = 1:6, c = 7:12)
+DT
+DT[ , { mySD = copy(.SD)
+ mySD[1, b := 99L]
+ mySD},
+ by = a]
+```
+
+## "no se puede cambiar el valor del enlace bloqueado para `.N`"
+
+Actualice a la versión 1.8.1 o posterior. A partir de esta versión, si `.N` se devuelve mediante `j`, se renombra como `N` para evitar ambigüedades en cualquier agrupación posterior entre la variable especial `.N` y una columna llamada `".N"`.
+
+El comportamiento anterior se puede reproducir forzando a que `.N` se llame `.N`, de la siguiente manera:
+
+```{r}
+DT = data.table(a = c(1,1,2,2,2), b = c(1,2,2,2,1))
+DT
+DT[ , list(.N = .N), list(a, b)] # show intermediate result for exposition
+cat(try(
+ DT[ , list(.N = .N), by = list(a, b)][ , unique(.N), by = a] # compound query more typical
+, silent = TRUE))
+```
+
+Si ya está ejecutando v1.8.1 o posterior, entonces el mensaje de error ahora es más útil que el error "no se puede cambiar el valor del enlace bloqueado", como puede ver arriba, ya que esta viñeta se produjo usando v1.8.1 o posterior.
+
+Ahora funciona la sintaxis más natural:
+
+```{r}
+if (packageVersion("data.table") >= "1.8.1") {
+ DT[ , .N, by = list(a, b)][ , unique(N), by = a]
+ }
+if (packageVersion("data.table") >= "1.9.3") {
+ DT[ , .N, by = .(a, b)][ , unique(N), by = a] # same
+}
+```
+
+# Mensajes de advertencia
+
+## "Los siguientes objetos están enmascarados de `paquete:base`: `cbind`, `rbind`"
+
+Esta advertencia solo aparecía en las versiones 1.6.5 y 1.6.6 al cargar el paquete. El objetivo era permitir que `cbind(DT, DF)` funcionara, pero resultó que esto interrumpía la compatibilidad total con el paquete `IRanges`. Actualice a la versión 1.6.7 o posterior.
+
+## "Se convirtió el RHS numérico a entero para que coincida con el tipo de la columna"
+
+Espero que esto se explique por sí solo. El mensaje completo es:
+
+Se ha convertido el RHS numérico a entero para que coincida con el tipo de la columna; puede tener precisión truncada. Cambie la columna a numérica primero creando un nuevo vector numérico de longitud 5 (n filas de toda la tabla) y asignándolo (es decir, "reemplazar columna"), o convierta el RHS a entero (por ejemplo, 1L o as.integer) para aclarar su intención (y para mayor rapidez). O bien, configure el tipo de columna correctamente desde el principio al crear la tabla y manténgalo.
+
+Para generarlo, prueba:
+
+```{r}
+DT = data.table(a = 1:5, b = 1:5)
+suppressWarnings(
+DT[2, b := 6] # works (slower) with warning
+)
+class(6) # numeric not integer
+DT[2, b := 7L] # works (faster) without warning
+class(7L) # L makes it an integer
+DT[ , b := rnorm(5)] # 'replace' integer column with a numeric column
+```
+
+## Lectura de data.table desde un archivo RDS o RData
+
+`*.RDS` y `*.RData` son tipos de archivo que permiten almacenar objetos R en memoria en disco de forma eficiente. Sin embargo, al almacenar `data.table` en un archivo binario, se pierde la sobreasignación de columnas (véase también `?truelength`). Esto no supone un gran problema: su `data.table` se copiará en memoria en la siguiente operación *por referencia* y generará una advertencia. Por lo tanto, se recomienda ejecutar `setDT()` en cada `data.table` cargado con `readRDS()` o `load()` para restaurar sus atributos internos. Si solo necesita preasignar espacio para nuevas columnas, también puede usar `setalloccol()`.
+
+Para obtener más detalles, consulte `?setDT` y `?truelength`.
+
+# Preguntas generales sobre el paquete
+
+## ¿Parece que la versión v1.3 falta en el archivo CRAN?
+
+Así es. La versión 1.3 solo estaba disponible en R-Forge. Se implementaron varios cambios importantes internamente, y las pruebas en desarrollo llevaron tiempo.
+
+## ¿Es data.table compatible con S-plus?
+
+No actualmente.
+
+ - Algunas partes principales del paquete están escritas en C y utilizan funciones y estructuras internas de R.
+ - El paquete utiliza alcance léxico, que es una de las diferencias entre R y **S-plus** explicadas en [R FAQ 3.3.1](https://cran.r-project.org/doc/FAQ/R-FAQ.html#Lexical-scoping)
+
+## ¿Está disponible para Linux, Mac y Windows?
+
+Sí, tanto para 32 bits como para 64 bits en todas las plataformas. Gracias a CRAN. No se utilizan bibliotecas especiales ni específicas del sistema operativo.
+
+## Me parece genial. ¿Qué puedo hacer?
+
+Envíe sugerencias, informes de errores y solicitudes de mejora a nuestro [seguimiento de problemas](https://github.com/Rdatatable/data.table/issues). Esto contribuye a mejorar el paquete.
+
+Por favor, marque el paquete con una estrella en [GitHub](https://github.com/Rdatatable/data.table). Esto anima a los desarrolladores y ayuda a otros usuarios de R a encontrarlo.
+
+Puede enviar solicitudes de extracción para cambiar el código y/o la documentación usted mismo; consulte nuestras [Pautas de contribución](https://github.com/Rdatatable/data.table/blob/master/.github/CONTRIBUTING.md).
+
+## No me parece bien. ¿Cómo puedo advertir a los demás sobre mi experiencia?
+
+Añadimos todos los artículos que conocemos (ya sean positivos o negativos) a la página [Artículos](https://github.com/Rdatatable/data.table/wiki/Articles). Todas las páginas de la wiki del proyecto en GitHub son de acceso abierto sin restricciones de modificación. Siéntase libre de escribir un artículo, enlazar a uno negativo que haya encontrado o añadir una nueva página a nuestra wiki para recopilar sus críticas. Por favor, que sea constructivo para que podamos mejorar.
+
+## Tengo una pregunta. Sé que la guía de publicación de r-help me indica que contacte al responsable (no a r-help), pero ¿hay algún grupo más amplio a quien pueda preguntar?
+
+Consulte la [guía de soporte](https://github.com/Rdatatable/data.table/wiki/Support) en la página de inicio del proyecto, que contiene enlaces actualizados.
+
+## ¿Dónde están los archivos de ayuda de datatable?
+
+La [página de inicio](https://github.com/Rdatatable/data.table/wiki) contiene enlaces a los archivos en varios formatos.
+
+## Preferiría no publicar en la página de Problemas, ¿puedo enviar un correo electrónico privado a una o dos personas?
+
+Claro. Sin embargo, es más probable que obtenga una respuesta más rápida en la página de Problemas o en Stack Overflow. Además, preguntar públicamente en esos lugares ayuda a ampliar la base de conocimientos general.
+
+## He creado un paquete que usa data.table. ¿Cómo puedo asegurarme de que mi paquete sea compatible con data.table para que la herencia de `data.frame` funcione?
+
+Consulte [esta respuesta](https://stackoverflow.com/a/10529888/403310).
+
+```{r, echo=FALSE}
+setDTthreads(.old.th)
+```
diff --git a/vignettes/es/datatable-fread-and-fwrite.Rmd b/vignettes/es/datatable-fread-and-fwrite.Rmd
new file mode 100644
index 000000000..539ca698c
--- /dev/null
+++ b/vignettes/es/datatable-fread-and-fwrite.Rmd
@@ -0,0 +1,295 @@
+---
+title: "Lectura y escritura rápida: fread()/fwrite()"
+date: "`r Sys.Date()`"
+output:
+ markdown::html_format
+vignette: >
+ %\VignetteIndexEntry{Fast Read and Fast Write}
+ %\VignetteEngine{litedown::vignette}
+ \usepackage[utf8]{inputenc}
+---
+
+```{r echo=FALSE, file='../_translation_links.R'}
+```
+
+`r .write.translation.links("Las traducciones de este documento están disponibles en: %s")`
+
+```{r, echo = FALSE, message = FALSE}
+require(data.table)
+litedown::reactor(comment = "# ")
+.old.th = setDTthreads(1)
+```
+
+Las funciones `fread()` y `fwrite()` del paquete `data.table` de R no solo están optimizadas para la velocidad con archivos grandes, sino que también ofrecen funciones potentes y prácticas para trabajar con conjuntos de datos pequeños. Esta viñeta destaca su usabilidad, flexibilidad y rendimiento para una importación y exportación de datos eficiente.
+
+***
+
+## 1. fread()
+
+### **1.1 Uso directo de herramientas de línea de comandos**
+
+La función `fread()` de `data.table` puede leer datos canalizados desde comandos de shell, lo que le permite filtrar o preprocesar datos incluso antes de que ingresen a R.
+
+```{r}
+# Create a sample file with some unwanted lines
+writeLines(
+'HEADER: Some metadata
+HEADER: More metadata
+1 2.0 3.0
+2 4.5 6.7
+HEADER: Yet more
+3 8.9 0.1
+4 1.2 3.4',
+"example_data.txt")
+
+library(data.table)
+fread("grep -v HEADER example_data.txt")
+```
+
+La opción `-v` hace que `grep` devuelva todas las líneas excepto aquellas que contienen la cadena 'HEADER'.
+
+> Dada la cantidad de ingenieros de alto nivel que han analizado la herramienta de comandos grep a lo largo de los años, es muy probable que sea la más rápida posible, además de ser correcta, práctica, estar bien documentada en línea y ser fácil de aprender y de buscar soluciones para tareas específicas. Si necesita realizar un filtrado de cadenas más complejo (por ejemplo, buscar cadenas al principio o al final de las líneas), la sintaxis de grep es muy potente. Aprender su sintaxis es una habilidad transferible a otros lenguajes y entornos.
+>
+> —Matt Dowle
+
+Mira este [ejemplo](https://stackoverflow.com/questions/36256706/fread-together-with-grepl/36270543#36270543) para obtener más detalles.
+
+En Windows, las herramientas de línea de comandos como `grep` están disponibles a través de diversos entornos, como Rtools, Cygwin o el Subsistema de Windows para Linux (WSL). En Linux y macOS, estas herramientas suelen estar incluidas en el sistema operativo.
+
+#### 1.1.1 Lectura directa de una cadena de texto
+
+`fread()` puede leer datos directamente de una cadena de caracteres en R usando el argumento `text`. Esto es especialmente útil para crear ejemplos reproducibles, probar fragmentos de código o trabajar con datos generados programáticamente en la sesión de R. Cada línea de la cadena debe estar separada por un carácter de nueva línea `\n`.
+
+```{r}
+my_data_string = "colA,colB,colC\n1,apple,TRUE\n2,banana,FALSE\n3,orange,TRUE"
+dt_from_text = fread(text = my_data_string)
+print(dt_from_text)
+```
+
+#### 1.1.2 Lectura desde URL
+
+`fread()` puede leer datos directamente de URLs web al pasar la URL como una cadena de caracteres a su argumento `file`. Esto permite descargar y leer datos de internet en un solo paso.
+
+```{r}
+# dt = fread("https://people.sc.fsu.edu/~jburkardt/data/csv/airtravel.csv")
+# print(dt)
+```
+
+#### 1.1.3 Descompresión automática de archivos comprimidos
+
+En muchos casos, `fread()` puede detectar y descomprimir automáticamente archivos con extensiones de compresión comunes, sin necesidad de un objeto de conexión explícito ni comandos de shell. Esto funciona comprobando la extensión del archivo.
+
+**Las extensiones compatibles generalmente incluyen:**
+- `.gz` / `.bz2` (gzip / bzip2): Compatible y funciona de inmediato.
+- `.zip` / `.tar` (archivos ZIP / tar, archivo único): Compatible: `fread()` leerá el primer archivo del archivo si solo hay un archivo presente.
+
+**Nota**: Si hay varios archivos en el archivo, `fread()` fallará con un error.
+
+### 1.2 Separador automático y detección de saltos
+
+`fread` automatiza la detección de delimitadores y encabezados, eliminando la necesidad de especificar manualmente en la mayoría de los casos. Simplemente proporcione el nombre del archivo; `fread` detecta la estructura de forma inteligente:
+
+**Detección de separadores**
+
+`fread` prueba separadores comunes (`,`,`\t`, `|`, espacio, `:`, `;`) y selecciona el que genera el número más consistente de campos en las filas muestreadas. Para delimitadores no estándar, puede anular esto con el parámetro `sep=`.
+
+**Detección de encabezado**
+
+Después de aplicar cualquier configuración `skip` o `nrows` (si se especifica), se examina la primera fila con una cantidad constante de campos:
+
+Si todos los campos de esta línea son interpretables como caracteres y los valores no se parecen mucho a una fila de datos (por ejemplo, una fila de cadenas de apariencia puramente numérica aún podría considerarse datos), normalmente se utiliza como encabezado (nombres de columnas).
+
+De lo contrario (por ejemplo, si la línea contiene tipos numéricos detectados o cadenas de caracteres que se parecen mucho a números y podrían ser datos), se trata como una fila de datos y se asignan nombres de columna predeterminados (`V1`, `V2`, …).
+
+Puedes decirle explícitamente a fread si existe un encabezado usando `header = TRUE` o `header = FALSE`.
+
+**Detección de saltos**
+
+De forma predeterminada (`skip="auto"`), `fread` omitirá automáticamente las líneas en blanco y las líneas de comentario (p. ej., las que empiezan por `#`) antes del encabezado de datos. Para especificar manualmente un número diferente de líneas a omitir, utilice
+
+* `skip=n` para omitir las primeras `n` líneas.
+* `skip="string"` para buscar una línea que contenga una subcadena (normalmente de los nombres de columna, como `skip="Date"`). La lectura comienza en la primera línea coincidente. Esto es útil para omitir metadatos o seleccionar subtablas en archivos multitabla. Esta función está inspirada en la función `read.xls` del paquete `gdata`.
+
+### 1.3 Detección automática de tipo de columna de alta calidad
+
+Muchos conjuntos de datos reales contienen columnas que inicialmente están en blanco, se rellenan con ceros o parecen numéricas, pero luego contienen caracteres. Para gestionar estas inconsistencias, `fread()` emplea una robusta estrategia de detección de tipos de columna.
+
+Desde la versión 1.10.5, `fread()` muestrea filas leyendo bloques de filas contiguas desde varios puntos equidistantes del archivo, incluyendo el inicio, el centro y el final. El número total de filas muestreadas se selecciona dinámicamente en función del tamaño y la estructura del archivo, y suele rondar las 10 000, aunque puede ser menor o ligeramente mayor. Este amplio muestreo ayuda a detectar cambios de tipo que se producen posteriormente en los datos (por ejemplo, de `001` a `0A0` o espacios en blanco que se rellenan).
+
+**Acceso eficiente a archivos con mmap**
+
+Para implementar este muestreo eficientemente, `fread()` utiliza el acceso a archivos mapeados en memoria del sistema operativo (`mmap`), lo que le permite saltar a posiciones arbitrarias en el archivo sin necesidad de escaneo secuencial. Esta estrategia perezosa y bajo demanda hace que el muestreo sea casi instantáneo, incluso para archivos muy grandes.
+
+Si un salto cae dentro de un campo entre comillas que incluye nuevas líneas, `fread()` prueba las líneas subsiguientes hasta que encuentra 5 filas consecutivas con la cantidad esperada de campos, lo que garantiza un análisis correcto incluso en archivos complejos.
+
+**Detección de tipos precisa y optimizada**
+
+El tipo de cada columna se infiere en función del tipo requerido más bajo de la siguiente lista ordenada:
+
+`lógico` < `entero` < `entero64` < `doble` < `carácter`
+
+Esto garantiza:
+
+- Asignación única de memoria por adelantado utilizando el tipo correcto
+- Evita tener que volver a leer el archivo o configurar manualmente `colClasses`
+- Mayor velocidad y eficiencia de la memoria
+
+**Excepciones de tipo fuera de muestra**
+
+Si se produce un cambio de tipo fuera de las filas muestreadas, `fread()` lo detecta automáticamente y relee el archivo para garantizar la correcta asignación de tipo, sin necesidad de intervención del usuario. Por ejemplo, una columna muestreada como entero podría contener posteriormente `00A`, lo que activaría una relectura automática como carácter.
+
+Toda la lógica de detección y cualquier relectura se detallan cuando `verbose=TRUE` está habilitado.
+
+### 1.4 Detección temprana de errores al final del archivo
+
+Dado que la muestra grande incluye explícitamente el final del archivo, se pueden detectar y reportar casi al instante problemas críticos, como un número inconsistente de columnas, un pie de página incorrecto o una comilla inicial sin su comilla de cierre correspondiente. Esta detección temprana de errores evita la sobrecarga innecesaria de procesar todo el archivo o asignar memoria excesiva para luego encontrar un fallo en el paso final. Garantiza una retroalimentación más rápida y un uso más eficiente de los recursos, especialmente al trabajar con grandes conjuntos de datos.
+
+### 1.5 Compatibilidad con `integer64`
+
+De forma predeterminada, `fread` detecta enteros mayores que 231 y los lee como `bit64::integer64` para mantener la precisión total. Este comportamiento se puede anular de tres maneras:
+
+- Por columna: utilice el argumento `colClasses` para especificar el tipo de columnas individuales.
+
+- Por llamada: use el argumento `integer64` en `fread()` para establecer cómo se leen todas las columnas `integer64` detectadas.
+
+- Globalmente: Establezca la opción `datatable.integer64` en su sesión R o en el archivo `.Rprofile` para cambiar el comportamiento predeterminado para todas las llamadas fread.
+
+El argumento integer64 (y la opción correspondiente) acepta los siguientes valores:
+
+- `"integer64"` (predeterminado): lee números enteros grandes como `bit64::integer64` con total precisión.
+
+- `"double"` o `"numeric"`: lee números enteros grandes como números de doble precisión, perdiendo potencialmente la precisión de forma silenciosa (similar a `utils::read.csv` en base R).
+
+- `"carácter"`: Lee números enteros grandes como cadenas de caracteres.
+
+Para comprobar o establecer el valor predeterminado global, utilice:
+
+```{r}
+# fread's default behavior is to treat large integers as "integer64"; however, this global setting can be changed:
+options(datatable.integer64 = "double") # Example: set globally to "double"
+getOption("datatable.integer64")
+```
+
+### 1.6 Eliminar o seleccionar columnas por nombre o posición
+
+Para ahorrar memoria y mejorar el rendimiento, utilice los argumentos `select` o `drop` de `fread()` para leer solo las columnas que necesita.
+
+- Si solo necesita unas pocas columnas, utilice `select`.
+- Si desea excluir solo algunas, utilice `drop`—esto evita tener que listar todo lo que desea conservar.
+
+Puntos clave:
+- `select`: Vector de nombres/posiciones de columnas a conservar (descarta las demás).
+- `drop`: Vector de nombres/posiciones de columnas a descartar (descarta las demás).
+- No utilice `select` y `drop` juntos, son mutuamente excluyentes.
+- `fread()` le avisará si falta alguna columna especificada en el archivo.
+
+Para obtener más detalles, consulte la página del manual ejecutando `?fread` en R.
+
+### 1.7 Detección automática de escape de comillas (incluida la ausencia de escape)
+
+`fread` detecta automáticamente cómo se escapan las comillas, incluidas las comillas dobles ("") o las comillas con barra invertida ("), sin necesidad de intervención del usuario. Esto se determina mediante una muestra amplia de datos (véase el punto 3) y se valida con todo el archivo.
+
+Escenarios admitidos:
+- Comillas sin escape dentro de campos entre comillas p. ej., `"Esta "comilla" no es válida, pero fread funciona de todos modos"` — admitido siempre que el recuento de columnas permanezca constante:
+
+```{r}
+data.table::fread(text='x,y\n"This "quote" is invalid, but fread works anyway",1')
+```
+
+- Campos sin comillas que comienzan con comillas p. ej., `Invalid"Field,10,20` — se reconoce correctamente como un campo no entre comillas.
+
+```{r}
+data.table::fread(text='x,y\nNot"Valid,1')
+```
+
+Requisitos y limitaciones:
+- Las reglas de escape y los recuentos de columnas deben ser consistentes en todo el archivo.
+
+- No compatible cuando `fill=TRUE` — en ese caso, el archivo debe seguir las comillas y el escape compatibles con RFC4180.
+
+Robustez específica de la versión: A partir de la versión 1.10.6, `fread` resuelve ambigüedades de forma más fiable en todo el archivo mediante la consistencia de recuento de columnas completo (el valor predeterminado es `fill=FALSE`). Se emiten advertencias si el análisis falla debido a una ambigüedad.
+
+## 2. fwrite()
+
+`fwrite()` es el complemento rápido para la escritura de archivos de `fread()`. Está diseñado para ofrecer velocidad, valores predeterminados sensatos y facilidad de uso, reflejando muchas de las ventajas de `fread`.
+
+### 2.1 Entrecomillado inteligente y minimalista (quote="auto")
+
+Cuando los datos se escriben como cadenas (ya sea de manera inherente, como columnas de caracteres, o por elección, como `dateTimeAs="ISO"`), `quote="auto"` (predeterminado) usa comillas en los campos de manera inteligente:
+
+**Entrecomillado contextual**: Los campos se ponen entre comillas solo cuando es necesario. Esto ocurre si contienen el delimitador `(sep)`, una comilla doble `(")`, un salto de línea `(\n)`, un retorno de carro `(\r)` o si el campo es una cadena vacía `("")`. La cadena vacía se entrecomilla para distinguirla de un valor NA al leer el archivo.
+
+**Omitido para salida numérica directa**: si se escriben columnas específicas como sus tipos numéricos subyacentes (por ejemplo, a través de `dateTimeAs="epoch"` para `POSIXct`, o si un usuario preconvierte Date a entero), entonces la lógica de comillas se omite naturalmente para esos campos numéricos, lo que contribuye a la eficiencia.
+
+```{r}
+dt_quoting_scenario = data.table(
+ text_field = c("Contains,a,comma", "Contains \"a quote\"", "Clean_text", "", NA),
+ numeric_field = 1:5
+)
+temp_quote_adv = tempfile(fileext = ".csv")
+
+fwrite(dt_quoting_scenario, temp_quote_adv)
+# Note the output: the empty string is quoted (""), but the NA is not.
+cat(readLines(temp_quote_adv), sep = "\n")
+```
+
+### 2.2 Serialización de fecha y hora de grano fino (argumento `dateTimeAs`)
+
+Ofrece un control preciso para los tipos POSIXct/Date:
+
+- `dateTimeAs="ISO"` (predeterminado para POSIXct): formato ISO 8601 (por ejemplo, AAAA-MM-DDTHH:MM:SS.ffffffZ), que conserva una precisión de subsegundos para un intercambio inequívoco.
+
+- `dateTimeAs="epoch"`: POSIXct como segundos desde la época (numérico).
+
+```{r}
+dt_timestamps = data.table(
+ ts = as.POSIXct("2023-10-26 14:35:45.123456", tz = "GMT"),
+ dt = as.Date("2023-11-15")
+)
+temp_dt_iso = tempfile(fileext = ".csv")
+fwrite(dt_timestamps, temp_dt_iso, dateTimeAs = "ISO")
+cat(readLines(temp_dt_iso), sep = "\n")
+unlink(temp_dt_iso)
+```
+
+### 2.3 Manejo de `bit64::integer64`
+
+**Precisión completa para enteros grandes**: `fwrite` escribe columnas `bit64::integer64` convirtiéndolas en cadenas con precisión completa. Esto evita la pérdida de datos o la conversión silenciosa a dobles que podrían ocurrir con escritores menos especializados. Esto es crucial para identificadores o mediciones que requieren un rango de enteros superior al estándar de R de `32 bits` o una precisión doble de `53 bits`.
+
+**Manejo directo**: Este manejo directo y cuidadoso de datos numéricos especializados garantiza la integridad de los datos y una E/S eficiente, sin conversiones intermedias innecesarias a tipos menos precisos.
+
+```{r}
+if (requireNamespace("bit64", quietly = TRUE)) {
+ dt_i64 = data.table(uid = bit64::as.integer64("1234567890123456789"), val = 100)
+ temp_i64_out = tempfile(fileext = ".csv")
+ fwrite(dt_i64, temp_i64_out)
+ cat(readLines(temp_i64_out), sep = "\n")
+ unlink(temp_i64_out)
+}
+```
+
+### 2.4 Orden de columnas y control de filtrado
+
+Para controlar el orden y el filtrado de columnas que se escriben en el archivo, filtre la `data.table` antes de llamar a `fwrite()`. El argumento `col.names` en `fwrite()` es un valor lógico (VERDADERO/FALSO) que controla si se escribe la fila del encabezado, no qué columnas se escriben.
+
+```{r}
+dt = data.table(A = 1:3, B = 4:6, C = 7:9)
+
+# Write only columns C and A, in that order
+fwrite(dt[, .(C, A)], "out.csv")
+cat(readLines("out.csv"), sep = "\n")
+file.remove("out.csv")
+```
+
+## 3. Una nota sobre el rendimiento
+
+Si bien esta viñeta se centra en las características y la facilidad de uso, la motivación principal para `fread` y `fwrite` es la velocidad.
+
+Para los usuarios interesados en comparaciones de rendimiento detalladas y actualizadas, recomendamos estas publicaciones de blog externas que utilizan el paquete `atime` para un análisis riguroso:
+
+- **[Tiempos asintóticos de data.table](https://tdhock.github.io/blog/2023/dt-atime-figures/)**: Compara el rendimiento de `fread` y `fwrite` con otros paquetes R populares como `readr` y `arrow`.
+- **[Evaluación comparativa de data.table con polares, duckdb y pandas](https://tdhock.github.io/blog/2024/pandas-dt/)**: Compara el rendimiento de E/S y agrupación de `data.table` con las principales bibliotecas de Python.
+
+Estos puntos de referencia muestran consistentemente que `fread` y `fwrite` son altamente competitivos y, a menudo, están a la vanguardia en términos de rendimiento en el ecosistema R.
+
+***
diff --git a/vignettes/es/datatable-importing.Rmd b/vignettes/es/datatable-importing.Rmd
new file mode 100644
index 000000000..75e9d6a9a
--- /dev/null
+++ b/vignettes/es/datatable-importing.Rmd
@@ -0,0 +1,298 @@
+---
+title: "Importar data.table"
+date: "`{r} Sys.Date()`"
+output:
+ litedown::html_format
+vignette: >
+ %\VignetteIndexEntry{Importing data.table}
+ %\VignetteEngine{litedown::vignette}
+ \usepackage[utf8]{inputenc}
+---
+
+```{r, echo = FALSE, message = FALSE}
+litedown::reactor(comment = "# ")
+.old.th = data.table::setDTthreads(1)
+```
+
+
+
+```{r, echo=FALSE, file='../_translation_links.R'}
+```
+
+`{r} .write.translation.links("Las traducciones de este documento están disponibles en: %s")`
+
+Este documento se centra en el uso de `data.table` como dependencia en otros paquetes R. Si está interesado en utilizar el código C de `data.table` desde una aplicación que no sea R, o en llamar directamente a sus funciones C, salte a la [última sección](#non-r-api) de esta viñeta.
+
+Importar `data.table` no es diferente de importar otros paquetes R. Esta viñeta tiene como objetivo responder las preguntas más comunes que surgen en torno a ese tema; las lecciones aquí presentadas se pueden aplicar a otros paquetes R.
+
+## ¿Por qué importar `data.table`?
+
+Una de las principales características de `data.table` es su sintaxis concisa, que agiliza y facilita la escritura y la comprensión del análisis exploratorio. Esta comodidad puede impulsar a los desarrolladores de paquetes a usar `data.table`. Otra razón, quizás más importante, es su alto rendimiento. Al externalizar tareas de computación pesadas de su paquete a `data.table`, generalmente se obtiene el máximo rendimiento sin necesidad de reinventar ninguno de estos trucos de optimización numérica.
+
+## Importar `data.table` es fácil
+
+Es muy fácil usar `data.table` como dependencia, ya que no tiene dependencias propias. Esto aplica tanto al sistema operativo como a las dependencias de R. Esto significa que si tiene R instalado en su equipo, ya tiene todo lo necesario para instalar `data.table`. Además, añadir `data.table` como dependencia de su paquete no generará una cadena de otras dependencias recursivas, lo que lo hace muy conveniente para la instalación sin conexión.
+
+## Archivo `DESCRIPTION` {#DESCRIPTION}
+
+El primer lugar para definir una dependencia en un paquete es el archivo `DESCRIPTION`. Normalmente, deberá agregar `data.table` en el campo `Imports:`. Para ello, deberá instalar `data.table` antes de que el paquete pueda compilarse/instalarse. Como se mencionó anteriormente, no se instalarán otros paquetes porque `data.table` no tiene dependencias propias. También puede especificar la versión mínima requerida de una dependencia; por ejemplo, si su paquete utiliza la función `fwrite`, introducida en `data.table` en la versión 1.9.8, debería incorporarla como `Imports: data.table (>= 1.9.8)`. De esta forma, puede asegurarse de que la versión de `data.table` instalada sea la 1.9.8 o posterior antes de que los usuarios puedan instalar el paquete. Además del campo `Imports:`, también puede usar `Depends: data.table`, pero desaconsejamos este método (y es posible que no lo permitamos en el futuro) porque carga `data.table` en el espacio de trabajo del usuario; es decir, habilita la funcionalidad de `data.table` en los scripts del usuario sin que este la solicite. `Imports:` es la forma correcta de usar `data.table` dentro del paquete sin afectar a `data.table` en el usuario. De hecho, esperamos que el campo `Depends:` quede obsoleto en R, ya que esto aplica a todos los paquetes.
+
+## Archivo `NAMESPACE` {#NAMESPACE}
+
+El siguiente paso es definir el contenido de `data.table` que usa tu paquete. Esto debe hacerse en el archivo `NAMESPACE`. Normalmente, los autores de paquetes usarán `import(data.table)`, que importará todas las funciones exportadas (es decir, las que aparecen en el archivo `NAMESPACE` de `data.table`) desde `data.table`.
+
+También puede usar solo un subconjunto de las funciones de `data.table`; por ejemplo, algunos paquetes pueden usar simplemente el lector y escritor de CSV de alto rendimiento de `data.table`, para lo cual puede agregar `importFrom(data.table, fread, fwrite)` en su archivo `NAMESPACE`. También es posible importar todas las funciones de un paquete, *excluyendo* algunas específicas, usando `import(data.table, except=c(fread, fwrite))`.
+
+Asegúrese de leer también la nota sobre la evaluación no estándar en `data.table` en [la sección sobre "globales indefinidos"](#globals).
+
+## Uso
+
+Como ejemplo, definiremos dos funciones en el paquete `a.pkg` que utilizan `data.table`. Una función, `gen`, generará un `data.table` simple; otra, `aggr`, realizará una agregación simple del mismo.
+
+```r
+gen = function (n = 100L) {
+ dt = as.data.table(list(id = seq_len(n)))
+ dt[, grp := ((id - 1) %% 26) + 1
+ ][, grp := letters[grp]
+ ][]
+}
+aggr = function (x) {
+ stopifnot(
+ is.data.table(x),
+ "grp" %in% names(x)
+ )
+ x[, .N, by = grp]
+}
+```
+
+## Pruebas
+
+Asegúrese de incluir pruebas en su paquete. Antes de cada lanzamiento principal de `data.table`, verificamos las dependencias inversas. Esto significa que si algún cambio en `data.table` pudiera afectar su código, podremos detectar los cambios problemáticos e informarle antes de publicar la nueva versión. Esto, por supuesto, supone que publicará su paquete en CRAN o Bioconductor. La prueba más básica puede ser un script de R en texto plano en el directorio `tests/test.R` de su paquete:
+
+```r
+library(a.pkg)
+dt = gen()
+stopifnot(nrow(dt) == 100)
+dt2 = aggr(dt)
+stopifnot(nrow(dt2) < 100)
+```
+
+Al probar su paquete, puede utilizar `R CMD check --no-stop-on-test-error`, que continuará después de un error y ejecutará todas sus pruebas (en lugar de detenerse en la primera línea del script que falló).
+
+## Pruebas usando `testthat`
+
+Es muy común usar el paquete `testthat` para realizar pruebas. Probar un paquete que importa `data.table` no es diferente a probar otros paquetes. Un ejemplo de script de prueba `tests/testthat/test-pkg.R`:
+
+```r
+context("pkg tests")
+
+test_that("generate dt", { expect_true(nrow(gen()) == 100) })
+test_that("aggregate dt", { expect_true(nrow(aggr(gen())) < 100) })
+```
+
+Si `data.table` está en "Suggests" (pero no en "Imports"), entonces necesita declarar `.datatable.aware=TRUE` en uno de los archivos R/* para evitar errores de "objeto no encontrado" al realizar pruebas a través de `testthat::test_package` o `testthat::test_check`.
+
+## Cómo lidiar con "undefined global functions or variables " {#globals}
+
+El uso de la evaluación diferida de R por parte de `data.table` (especialmente en el lado izquierdo de `:=`) no es bien reconocido por `R CMD check`. Esto genera `NOTE`s como la siguiente durante la comprobación del paquete:
+
+```
+* checking R code for possible problems ... NOTE
+aggr: no visible binding for global variable 'grp'
+gen: no visible binding for global variable 'grp'
+gen: no visible binding for global variable 'id'
+Undefined global functions or variables:
+grp id
+```
+
+La forma más sencilla de solucionar esto es predefinir esas variables dentro del paquete y establecerlas como `NULL`, añadiendo opcionalmente un comentario (como se hace en la versión refinada de `gen` a continuación). Siempre que sea posible, también puede usar un vector de caracteres en lugar de símbolos (como en `aggr` a continuación):
+
+```r
+gen = function (n = 100L) {
+ id = grp = NULL # due to NSE notes in R CMD check
+ dt = as.data.table(list(id = seq_len(n)))
+ dt[, grp := ((id - 1) %% 26) + 1
+ ][, grp := letters[grp]
+ ][]
+}
+aggr = function (x) {
+ stopifnot(
+ is.data.table(x),
+ "grp" %in% names(x)
+ )
+ x[, .N, by = "grp"]
+}
+```
+
+El caso de los símbolos especiales de `data.table` (p. ej., `.SD` y `.N`) y el operador de asignación (`:=`) es ligeramente diferente (consulte `?.N` para obtener más información, incluyendo una lista completa de dichos símbolos). Debe importar cualquiera de estos valores que utilice del espacio de nombres de `data.table` para evitar problemas derivados del improbable escenario de que cambiemos el valor exportado de estos en el futuro. Por ejemplo, si desea usar `.N`, `.I` y `:=`, un `NAMESPACE` mínimo tendría:
+
+```r
+importFrom(data.table, .N, .I, ':=')
+```
+
+Mucho más simple es simplemente usar `import(data.table)`, lo que permitirá el uso en el código de su paquete de cualquier objeto exportado desde `data.table`.
+
+Si no le importa tener `id` y `grp` registrados como variables globales en el espacio de nombres de su paquete, puede usar `?globalVariables`. Tenga en cuenta que estas notas no afectan el código ni su funcionalidad; si no va a publicar su paquete, puede simplemente ignorarlas.
+
+## Se debe tener cuidado al proporcionar y utilizar `options`
+
+Una práctica común en los paquetes de R es proporcionar opciones de personalización definidas por `options(name=val)` y obtenidas mediante `getOption("name", default)`. Los argumentos de función suelen especificar una llamada a `getOption()` para que el usuario conozca (a través de `?fun` o `args(fun)`) el nombre de la opción que controla el valor predeterminado para ese parámetro; por ejemplo, `fun(..., verbose=getOption("datatable.verbose", FALSE))`. Todas las opciones de `data.table` comienzan con `datatable.` para evitar conflictos con las opciones de otros paquetes. El usuario simplemente llama a `options(datatable.verbose=TRUE)` para activar la verbosidad. Esto afecta a todas las llamadas a la función data.table, a menos que `verbose=FALSE` se especifique explícitamente; por ejemplo, `fun(..., verbose=FALSE)`.
+
+El mecanismo de opciones en R es *global*. Esto significa que si un usuario establece una opción `data.table` para su propio uso, esa configuración también afecta al código dentro de cualquier paquete que también esté usando `data.table`. Para una opción como `datatable.verbose`, este es exactamente el comportamiento deseado ya que el deseo es rastrear y registrar todas las operaciones de `data.table` desde donde sea que se originen; activar la verbosidad no afecta los resultados. Otra opción única de R y excelente para producción es `options(warn=2)` de R que convierte todas las advertencias en errores. Nuevamente, el deseo es afectar cualquier advertencia en cualquier paquete para no perder ninguna advertencia en producción. Hay 6 opciones `datatable.print.*` y 3 opciones de optimización que no afectan el resultado de las operaciones. Sin embargo, hay una opción `data.table` que sí afecta y ahora es una preocupación: `datatable.nomatch`. Esta opción cambia la unión predeterminada de externa a interna. [Aparte, la unión predeterminada es externa porque externa es más segura; no elimina los datos faltantes silenciosamente; Además, es coherente con el método R básico para la coincidencia por nombres e índices. Algunos usuarios prefieren que la unión interna sea la opción predeterminada, y les proporcionamos esta opción. Sin embargo, si un usuario configura esta opción, puede cambiar involuntariamente el comportamiento de las uniones dentro de paquetes que usan `data.table`. Por consiguiente, en la versión 1.12.4 (octubre de 2019) se mostraba un mensaje al usar la opción `datatable.nomatch`, y a partir de la versión 1.14.2, se ignora con una advertencia. Era la única opción de `data.table` con este problema.
+
+## Solución de problemas
+
+Si enfrenta algún problema al crear un paquete que usa data.table, confirme que el problema se pueda reproducir en una sesión R limpia usando la consola R: `R CMD check package.name`.
+
+Algunos de los problemas más comunes que enfrentan los desarrolladores suelen estar relacionados con las herramientas auxiliares diseñadas para automatizar algunas tareas de desarrollo de paquetes; por ejemplo, usar `roxygen` para generar el archivo `NAMESPACE` a partir de los metadatos de los archivos de código de R. Otros están relacionados con las herramientas auxiliares que compilan y verifican el paquete. Desafortunadamente, estas herramientas auxiliares a veces tienen efectos secundarios imprevistos u ocultos que pueden ocultar el origen de los problemas. Por lo tanto, asegúrese de verificar con la consola de R (ejecute R en la línea de comandos) y asegúrese de que la importación esté definida en los archivos `DESCRIPTION` y `NAMESPACE` siguiendo las instrucciones [arriba](#DESCRIPTION).
+
+Si no puede reproducir los problemas que tiene al usar la compilación y verificación de la consola R simple, puede intentar obtener ayuda en función de los problemas anteriores que hemos encontrado con la interacción de `data.table` con las herramientas auxiliares: [devtools#192](https://github.com/r-lib/devtools/issues/192) o [devtools#1472](https://github.com/r-lib/devtools/issues/1472).
+
+## Licencia
+
+Desde la versión 1.10.5, `data.table` se licencia como Licencia Pública de Mozilla (MPL). Las razones del cambio de la GPL se pueden consultar aquí [https://github.com/Rdatatable/data.table/pull/2456] y se puede leer más sobre la MPL en Wikipedia [https://en.wikipedia.org/wiki/Mozilla_Public_License] y [https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses].
+
+## Importar opcionalmente `data.table`: Sugiere
+
+Si desea usar `data.table` condicionalmente, es decir, solo cuando esté instalado, debe usar `Suggests: data.table` en su archivo `DESCRIPTION` en lugar de `Imports: data.table`. De forma predeterminada, esta definición no forzará la instalación de `data.table` al instalar el paquete. Esto también requiere que use `data.table` condicionalmente en el código del paquete, lo cual debe hacerse mediante la función `?requireNamespace`. El siguiente ejemplo muestra el uso condicional del rápido escritor de CSV `?fwrite` de `data.table`. Si el paquete `data.table` no está instalado, se usa la función `?write.table` de R, mucho más lenta.
+
+```r
+my.write = function (x) {
+ if(requireNamespace("data.table", quietly=TRUE)) {
+ data.table::fwrite(x, "data.csv")
+ } else {
+ write.table(x, "data.csv")
+ }
+}
+```
+
+Una versión ligeramente más extendida de esto también garantizaría que la versión instalada de `data.table` sea lo suficientemente reciente para tener la función `fwrite` disponible:
+
+```r
+my.write = function (x) {
+ if(requireNamespace("data.table", quietly=TRUE) &&
+ utils::packageVersion("data.table") >= "1.9.8") {
+ data.table::fwrite(x, "data.csv")
+ } else {
+ write.table(x, "data.csv")
+ }
+}
+```
+
+Al usar un paquete como dependencia sugerida, no debe importarlo en el archivo `NAMESPACE`. Simplemente menciónelo en el archivo `DESCRIPTION`. Al usar funciones `data.table` en el código del paquete (archivos R/*), debe usar el prefijo `data.table::`, ya que ninguna se importa. Al usar `data.table` en pruebas de paquetes (por ejemplo, archivos tests/testthat/test*), debe declarar `.datatable.aware=TRUE` en uno de los archivos R/*.
+
+## `data.table` en `Imports` pero no se importó nada
+
+Algunos usuarios ([por ejemplo](https://github.com/Rdatatable/data.table/issues/2341)) pueden preferir evitar el uso de `importFrom` o `import` en su archivo `NAMESPACE` y en su lugar usar la calificación `data.table::` en todo el código interno (por supuesto, manteniendo `data.table` debajo de su `Imports:` en `DESCRIPTION`).
+
+En este caso, la función no exportada `[.data.table` volverá a llamar a `[.data.frame` como medida de protección, ya que `data.table` no tiene forma de saber que el paquete padre es consciente de que está intentando realizar llamadas contra la sintaxis de la API de consulta de `data.table` (lo que podría generar un comportamiento inesperado ya que la estructura de las llamadas a `[.data.frame` y `[.data.table` difieren fundamentalmente, por ejemplo, este último tiene muchos más argumentos).
+
+Si este es su enfoque preferido para el desarrollo de paquetes, defina `.datatable.aware = TRUE` en cualquier parte de su código fuente de R (no es necesario exportar). Esto indica a `data.table` que usted, como desarrollador de paquetes, ha diseñado su código para que utilice intencionalmente su funcionalidad, aunque no sea evidente al inspeccionar su archivo `NAMESPACE`.
+
+`data.table` determina sobre la marcha si la función que llama es consciente de que está accediendo a `data.table` con la función interna `cedta` (**C**alling **E**nvironment is **D**ata **T**able **A**ware), que, además de verificar `?getNamespaceImports` para su paquete, también verifica la existencia de esta variable (entre otras cosas).
+
+## Más información sobre las dependencias
+
+Para obtener documentación más canónica sobre la definición de dependencia de paquetes, consulte el manual oficial: [Escritura de extensiones R](https://cran.r-project.org/doc/manuals/r-release/R-exts.html).
+
+## Importación de rutinas data.table C
+
+Algunas de las rutinas C utilizadas internamente ahora se exportan a nivel C, por lo que se pueden usar en paquetes R directamente desde su código C. Consulte [`?cdt`](https://rdatatable.gitlab.io/data.table/reference/cdt.html) para obtener detalles y la sección [Escritura de extensiones R](https://cran.r-project.org/doc/manuals/r-release/R-exts.html) *Enlace a rutinas nativas en otros paquetes* para su uso.
+
+## Importación desde aplicaciones que no son r {#non-r-api}
+
+Algunas pequeñas partes del código C de `data.table` se aislaron de la API de RC y ahora pueden usarse desde aplicaciones que no sean de R mediante enlaces a archivos .so o .dll. Más adelante se proporcionarán detalles más concretos al respecto; por ahora, puede estudiar el código C aislado de la API de RC en [src/fread.c](https://github.com/Rdatatable/data.table/blob/master/src/fread.c) y [src/fwrite.c](https://github.com/Rdatatable/data.table/blob/master/src/fwrite.c).
+
+## Cómo convertir su dependencia Depends en data.table a Imports
+
+Para convertir una dependencia `Depends` de `data.table` en una dependencia `Imports` en su paquete, siga estos pasos:
+
+### Paso 0. Asegúrese de que su paquete pase la verificación R CMD inicialmente
+
+### Paso 1. Actualice el archivo DESCRIPTION para colocar data.table en Imports, no en Depends
+
+**Antes:**
+
+```dcf
+Depends:
+ R (>= 3.5.0),
+ data.table
+Imports:
+```
+
+**Después:**
+
+```dcf
+Depends:
+ R (>= 3.5.0)
+Imports:
+ data.table
+```
+
+### Paso 2.1: Ejecutar `R CMD check`
+
+Ejecute `R CMD check` para identificar importaciones o símbolos faltantes. Este paso ayuda a:
+
+- Detecta automáticamente cualquier función o símbolo de `data.table` que no se importe explícitamente.
+- Marca los símbolos especiales faltantes como `.N`, `.SD` y `:=`.
+- Proporciona retroalimentación inmediata sobre lo que se debe agregar al archivo NAMESPACE.
+
+Nota: No todos estos usos son detectados por `R CMD check`. En particular, `R CMD check` omite algunos símbolos/funciones en fórmulas y no detecta expresiones analizadas como `parse(text = "data.table(a = 1)")`. Los paquetes necesitarán una buena cobertura de pruebas para detectar estos casos extremos.
+
+### Paso 2.2: Modificar el archivo NAMESPACE
+
+Según los resultados de `R CMD check`, asegúrese de que se importen todas las funciones utilizadas, los símbolos especiales, los genéricos S3 y las clases S4 de `data.table`.
+
+Esto implica agregar directivas `importFrom(data.table, ...)` para símbolos, funciones y genéricos de S3, o directivas `importClassesFrom(data.table, ...)` para clases de S4, según corresponda. Consulte "Escritura de extensiones de R" para obtener más información sobre cómo hacerlo correctamente.
+
+#### Importación completa
+
+Como alternativa, puede importar todas las funciones de `data.table` a la vez, aunque esto generalmente no se recomienda:
+
+```r
+import(data.table)
+```
+
+**Justificación para evitar importaciones generales:**
+1. **Documentación**: El archivo NAMESPACE puede servir como buena documentación de cómo depende de ciertos paquetes.
+2. **Evitar conflictos**: Las importaciones generales pueden causar fallos sutiles. Por ejemplo, si importa `import(pkgA)` e `import(pkgB)`, pero posteriormente pkgB exporta una función también exportada por pkgA, esto romperá su paquete debido a conflictos en su espacio de nombres, lo cual no está permitido por `R CMD check` y CRAN.
+
+### Paso 3: Actualice sus archivos de código R fuera del directorio R/ del paquete
+
+Al mover un paquete de "Depends" a "Imports", ya no se adjuntará automáticamente al cargarlo. Esto puede ser importante para ejemplos, pruebas, viñetas y demostraciones, donde los paquetes de "Imports" deben adjuntarse explícitamente.
+
+**Antes (con `Depends`):**
+
+```r
+# data.table functions are directly available
+library(MyPkgDependsDataTable)
+dt <- data.table(x = 1:10, y = letters[1:10])
+setDT(dt)
+result <- merge(dt, other_dt, by = "x")
+```
+
+**Después (con `Imports`):**
+
+```r
+# Explicitly load data.table in user scripts or vignettes
+library(data.table)
+library(MyPkgDependsDataTable)
+dt <- data.table(x = 1:10, y = letters[1:10])
+setDT(dt)
+result <- merge(dt, other_dt, by = "x")
+```
+
+### Beneficios de usar `Imports`
+
+- **Facilidad de uso**: `Depends` modifica la ruta `search()` de los usuarios, posiblemente sin su consentimiento.
+- **Gestión del espacio de nombres**: Solo están disponibles las funciones que tu paquete importa explícitamente, lo que reduce el riesgo de conflictos de nombres de funciones.
+- **Carga de paquetes más limpia**: Las dependencias de tu paquete no se vinculan a la ruta de búsqueda, lo que hace que el proceso de carga sea más limpio y potencialmente más rápido.
+- **Mantenimiento más sencillo**: Simplifica las tareas de mantenimiento a medida que evolucionan las API de las dependencias ascendentes. Depender demasiado de `Depends` puede generar conflictos y problemas de compatibilidad con el tiempo.
+
+```{r, echo = FALSE, message = FALSE}
+data.table::setDTthreads(.old.th)
+```
diff --git a/vignettes/es/datatable-intro.Rmd b/vignettes/es/datatable-intro.Rmd
new file mode 100644
index 000000000..a9b5230b1
--- /dev/null
+++ b/vignettes/es/datatable-intro.Rmd
@@ -0,0 +1,726 @@
+---
+title: "Introducción a data.table"
+date: "`{r} Sys.Date()`"
+output:
+ litedown::html_format
+vignette: >
+ %\VignetteIndexEntry{Introduction to data.table}
+ %\VignetteEngine{litedown::vignette}
+ \usepackage[utf8]{inputenc}
+---
+
+```{r, echo=FALSE, file='../_translation_links.R'}
+```
+
+`{r} .write.translation.links("Las traducciones de este documento están disponibles en: %s")`
+
+```{r, echo = FALSE, message = FALSE}
+library(data.table)
+litedown::reactor(comment = "# ")
+.old.th = setDTthreads(1)
+```
+
+Esta viñeta presenta la sintaxis de `data.table`, su forma general, cómo filtrar filas, seleccionar y calcular columnas, y realizar agregaciones por grupo. Estar familiarizado con la estructura de datos `data.frame` de R base es útil, pero no es esencial para seguir esta viñeta.
+
+***
+
+## Análisis de datos utilizando `data.table`
+
+Las operaciones de manipulación de datos como *filtro*, *agrupación*, *actualización*, *unión*, etc., están intrínsecamente relacionadas. Mantener estas *operaciones relacionadas* permite:
+
+* Sintaxis *concisa* y *consistente* independientemente del conjunto de operaciones que desee realizar para lograr su objetivo final.
+
+* realizar análisis *de manera fluida* sin la carga cognitiva de tener que asignar cada operación a una función particular de un conjunto potencialmente enorme de funciones disponibles antes de realizar el análisis.
+
+* *optimizando automáticamente* las operaciones de manera interna y muy efectiva al conocer con precisión los datos necesarios para cada operación, lo que genera un código muy rápido y con uso eficiente de la memoria.
+
+En resumen, si le interesa reducir drásticamente el tiempo de *programación* y *computación*, este paquete es para usted. La filosofía de `data.table` lo hace posible. Nuestro objetivo es ilustrarlo mediante esta serie de viñetas.
+
+## Datos {#data}
+
+En esta viñeta, utilizaremos datos de [NYC-flights14](https://raw.githubusercontent.com/Rdatatable/data.table/master/vignettes/flights14.csv) obtenidos del paquete [flights](https://github.com/arunsrinivasan/flights) (disponible solo en GitHub). Este paquete contiene datos de vuelos puntuales de la Oficina de Estadísticas de Transporte para todos los vuelos que salieron de los aeropuertos de la ciudad de Nueva York en 2014 (inspirado en [nycflights13](https://github.com/tidyverse/nycflights13)). Los datos solo están disponibles para el período de enero a octubre de 2014.
+
+Podemos usar el lector de archivos rápido y fácil de usar `fread` de `data.table` para cargar `flights` directamente de la siguiente manera:
+
+```{r, echo = FALSE}
+options(width = 100L)
+```
+
+```{r}
+input <- if (file.exists("../flights14.csv")) {
+ "../flights14.csv"
+} else {
+ "https://raw.githubusercontent.com/Rdatatable/data.table/master/vignettes/flights14.csv"
+}
+flights <- fread(input)
+flights
+dim(flights)
+```
+
+Nota: `fread` acepta URLs `http` y `https` directamente, así como comandos del sistema operativo como `sed` y `awk`. Consulte `?fread` para ver ejemplos.
+
+## Introducción
+
+En esta viñeta, vamos a:
+
+1. Comience con lo básico: qué es una `data.table`, su formato general, cómo *filtrar* filas, cómo seleccionar y calcular columnas;
+
+2. Luego, veremos cómo realizar agregaciones de datos por grupo
+
+## 1. Conceptos básicos {#basics-1}
+
+### a) ¿Qué es `data.table`? {#what-is-datatable-1a}
+
+`data.table` es un paquete de R que proporciona **una versión mejorada** de un `data.frame`, la estructura de datos estándar para almacenar datos en `base` R. En la sección [Data](#data) anterior, vimos cómo crear un `data.table` usando `fread()`, pero también podemos crear uno usando la función `data.table()`. Aquí hay un ejemplo:
+
+```{r}
+DT = data.table(
+ ID = c("b","b","b","a","a","c"),
+ a = 1:6,
+ b = 7:12,
+ c = 13:18
+)
+DT
+class(DT$ID)
+```
+
+También puede convertir objetos existentes a una tabla `data.table` mediante `setDT()` (para estructuras `data.frame` y `list`) o `as.data.table()` (para otras estructuras). Para más detalles sobre la diferencia (que excede el alcance de este artículo), consulte `?setDT` y `?as.data.table`.
+
+#### Tenga en cuenta que:
+
+* Los números de fila se imprimen con un `:` para separar visualmente el número de fila de la primera columna.
+
+* Cuando el número de filas a imprimir excede la opción global `datatable.print.nrows` (predeterminado = `r getOption("datatable.print.nrows")`), se imprimen automáticamente solo las 5 primeras y las 5 últimas filas (como se puede ver en la sección [Data](#data)). Con un `data.frame` grande, es posible que haya tenido que esperar mientras tablas más grandes se imprimen y paginan, a veces sin parar. Esta restricción ayuda con esto, y puede consultar el número predeterminado de la siguiente manera:
+
+ ```{.r}
+ getOption("datatable.print.nrows")
+ ```
+
+* `data.table` nunca establece ni usa *nombres de fila*. Veremos por qué en la viñeta [`vignette("datatable-keys-fast-subset", package="data.table")`](datatable-keys-fast-subset.html).
+
+### b) Forma general: ¿de qué manera se *mejora* una `data.table`? {#enhanced-1b}
+
+A diferencia de un `data.frame`, se puede hacer *mucho más* que simplemente filtrar filas y seleccionar columnas dentro del marco de un `data.table`, es decir, dentro de `[ ... ]` (Nota: también podríamos referirnos a escribir dentro de `DT[...]` como "consultar `DT`", como analogía o en relación con SQL). Para comprenderlo, primero debemos analizar la *forma general* de la sintaxis de `data.table`, como se muestra a continuación:
+
+```r
+DT[i, j, by]
+
+## R: i j by
+## SQL: where | order by select | update group by
+```
+
+Los usuarios con conocimientos de SQL probablemente se sentirán inmediatamente identificados con esta sintaxis.
+
+#### La forma de leerlo (en voz alta) es:
+
+Tomar `DT`, filtrar/reordenar filas usando `i`, luego calcular `j`, agrupado por `by`.
+
+Comencemos mirando primero `i` y `j`: filtrando filas y operando en columnas.
+
+### c) Filtrar filas en `i` {#subset-i-1c}
+
+#### -- Obtenga todos los vuelos con "JFK" como aeropuerto de origen en el mes de junio.
+
+```{r}
+ans <- flights[origin == "JFK" & month == 6L]
+head(ans)
+```
+
+* Dentro de una tabla `data.table`, se puede hacer referencia a las columnas *como si fueran variables*, de forma similar a SQL o Stata. Por lo tanto, simplemente nos referimos a `origin` y `month` como si fueran variables. No es necesario añadir el prefijo `flights$` cada vez. Sin embargo, usar `flights$origin` y `flights$month` funcionaría perfectamente.
+
+* Se calculan los *índices de fila* que satisfacen la condición `origin == "JFK" & month == 6L` y, como no queda nada más por hacer, todas las columnas de `flights` en las filas correspondientes a esos *índices de fila* simplemente se devuelven como una `data.table`.
+
+* No se requiere una coma después de la condición en `i`. Pero `flights[origin == "JFK" & month == 6L, ]` funcionaría perfectamente. Sin embargo, en un `data.frame`, la coma es necesaria.
+
+#### -- Obtener las dos primeras filas de `vuelos`. {#subset-rows-integer}
+
+```{r}
+ans <- flights[1:2]
+ans
+```
+
+* En este caso, no hay ninguna condición. Los índices de fila ya se proporcionan en `i`. Por lo tanto, devolvemos una `data.table` con todas las columnas de `flights` en las filas para esos *índices de fila*.
+
+#### -- Ordena `vuelos` primero por la columna `origen` en orden *ascendente*, y luego por `dest` en orden *descendente*:
+
+Podemos utilizar la función R `order()` para lograr esto.
+
+```{r}
+ans <- flights[order(origin, -dest)]
+head(ans)
+```
+
+#### `order()` está optimizado internamente
+
+* Podemos usar "-" en columnas de `carácter` dentro del marco de una `data.table` para ordenar en orden decreciente.
+
+* Además, `order(...)` dentro del marco de `data.table` utiliza el ordenamiento radix rápido interno de `data.table`, `forder()`. Este ordenamiento proporcionó una mejora tan convincente respecto a `base::order` de R que el proyecto R adoptó el algoritmo `data.table` como su ordenamiento predeterminado en 2016 para R 3.3.0 (para referencia, consulte `?sort` y las [NOTICIAS de la versión de R](https://cran.r-project.org/doc/manuals/r-release/NEWS.pdf)).
+
+Discutiremos el orden rápido de `data.table` con más detalle en la viñeta *internos de `data.table`*.
+
+### d) Seleccione la(s) columna(s) en `j` {#select-j-1d}
+
+#### -- Seleccione la columna `arr_delay`, pero devuélvala como un *vector*.
+
+```{r}
+ans <- flights[, arr_delay]
+head(ans)
+```
+
+* Dado que las columnas se pueden referenciar como variables dentro de una tabla `data.table`, nos referimos directamente a la *variable* que queremos filtrar. Como queremos *todas las filas*, simplemente omitimos `i`.
+
+* Devuelve *todas* las filas de la columna `arr_delay`.
+
+#### -- Seleccione la columna `arr_delay`, pero devuélvala como `data.table` en su lugar.
+
+```{r}
+ans <- flights[, list(arr_delay)]
+head(ans)
+```
+
+* Envolvemos las *variables* (nombres de columna) dentro de `list()`, lo que garantiza que se devuelva `data.table`. En el caso de un solo nombre de columna, al no envolver con `list()` se devuelve un vector, como se vio en el [ejemplo anterior](#select-j-1d).
+
+* `data.table` también permite encapsular columnas con `.()` en lugar de `list()`. Es un *alias* de `list()`; ambos significan lo mismo. Puedes usar el que prefieras; hemos notado que la mayoría de los usuarios prefieren `.()` por concisión, por lo que seguiremos usando `.()` de aquí en adelante.
+
+Un `data.table` (y también un `data.frame`) es internamente una `lista`, con la condición de que cada elemento tenga la misma longitud y que la `lista` tenga un atributo `class`. Permitir que `j` devuelva una `lista` permite convertir y devolver `data.table` de forma muy eficiente.
+
+#### Consejo: {#tip-1}
+
+Mientras `j-expression` devuelva una `list`, cada elemento de la lista se convertirá en una columna en la `data.table` resultante. Esto hace que `j` sea bastante potente, como veremos en breve. También es muy importante comprender esto para cuando se deseen realizar consultas más complejas.
+
+#### - Seleccione las columnas `arr_delay` y `dep_delay`.
+
+```{r}
+ans <- flights[, .(arr_delay, dep_delay)]
+head(ans)
+
+## alternatively
+# ans <- flights[, list(arr_delay, dep_delay)]
+```
+
+* Envuelve ambas columnas dentro de `.()` o `list()`. Listo.
+
+#### -- Seleccione las columnas `arr_delay` y `dep_delay` *y* cámbieles el nombre a `delay_arr` y `delay_dep`.
+
+Dado que `.()` es solo un alias de `list()`, podemos nombrar las columnas como lo haríamos al crear una `lista`.
+
+```{r}
+ans <- flights[, .(delay_arr = arr_delay, delay_dep = dep_delay)]
+head(ans)
+```
+
+### e) Calcular o *hacer* en `j`
+
+#### --¿Cuántos viajes han tenido un retraso total < 0?
+
+```{r}
+ans <- flights[, sum( (arr_delay + dep_delay) < 0 )]
+ans
+```
+
+#### ¿Que está pasando aquí?
+
+* La función `j` de `data.table` puede gestionar más que simplemente *seleccionar columnas*; también puede gestionar *expresiones*, es decir, *calcular sobre columnas*. Esto no debería sorprender, ya que se puede hacer referencia a las columnas como si fueran variables. Entonces, deberíamos poder *calcular* invocando funciones sobre esas variables. Y eso es precisamente lo que ocurre aquí.
+
+### f) Filtrar en `i` *y* en `j`
+
+#### -- Calcular el retraso promedio de llegada y salida para todos los vuelos con aeropuerto de origen "JFK" en el mes de junio.
+
+```{r}
+ans <- flights[origin == "JFK" & month == 6L,
+ .(m_arr = mean(arr_delay), m_dep = mean(dep_delay))]
+ans
+```
+
+* Primero filtramos en `i` para encontrar los *índices de fila* coincidentes donde `origen` aeropuerto es igual a `"JFK"` y `mes` es igual a `6L`. *Aún* no filtramos *data.table` *completa* correspondiente a esas filas.
+
+* Ahora, analizamos `j` y descubrimos que solo usa *dos columnas*. Lo que tenemos que hacer es calcular su `media()`. Por lo tanto, filtramos solo las columnas correspondientes a las filas coincidentes y calculamos su media (`mean()`).
+
+Dado que los tres componentes principales de la consulta (`i`, `j` y `by`) están *juntos* dentro de `[...]`, `data.table` puede verlos a los tres y optimizar la consulta en su conjunto *antes de la evaluación*, en lugar de optimizar cada uno por separado. Por lo tanto, podemos evitar el filtrado completo (es decir, subdividir las columnas *además de* `arr_delay` y `dep_delay`), tanto por velocidad como por eficiencia de memoria.
+
+#### --¿Cuántos viajes se han realizado en el año 2014 desde el aeropuerto “JFK” en el mes de junio?
+
+```{r}
+ans <- flights[origin == "JFK" & month == 6L, length(dest)]
+ans
+```
+
+La función `length()` requiere un argumento de entrada. Solo necesitamos calcular el número de filas del subconjunto. Podríamos haber usado cualquier otra columna como argumento de entrada para `length()`. Este enfoque recuerda a `SELECT COUNT(dest) FROM flights WHERE origin = 'JFK' AND month = 6` en SQL.
+
+Este tipo de operación ocurre con bastante frecuencia, especialmente durante la agrupación (como veremos en la siguiente sección), hasta el punto que `data.table` proporciona un *símbolo especial* `.N` para ello.
+
+### g) Manejar elementos inexistentes en `i`
+
+#### --¿Qué sucede cuando se consultan elementos inexistentes?
+
+Al consultar una `data.table` en busca de elementos que no existen, el comportamiento difiere según el método utilizado.
+
+```r
+setkeyv(flights, "origin")
+```
+
+* **Filtro basado en clave: `dt["d"]`**
+
+Esto realiza una unión a la derecha en la columna de clave `x`, lo que genera una fila con `d` y `NA` para las columnas no encontradas. Al usar `setkeyv`, la tabla se ordena según las claves especificadas y se crea un índice interno, lo que permite la búsqueda binaria para una subdivisión eficiente.
+
+```r
+flights["XYZ"]
+# Returns:
+# origin year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum ...
+# 1: XYZ NA NA NA NA NA NA NA NA NA NA NA NA ...
+```
+
+* **Filtro lógico: `dt[x == "d"]`**
+
+Esto realiza una operación filtrado estándar que no encuentra ninguna fila coincidente y, por lo tanto, devuelve una `data.table` vacía.
+
+```r
+ flights[origin == "XYZ"]
+# Returns:
+# Empty data.table (0 rows and 19 cols): year,month,day,dep_time,sched_dep_time,dep_delay,arr_time,sched_arr_time,arr_delay,...
+```
+
+* **Coincidencia exacta usando `nomatch=NULL`**
+
+Para coincidencias exactas sin `NA` para elementos inexistentes, utilice `nomatch=NULL`:
+
+```r
+flights["XYZ", nomatch=NULL]
+# Returns:
+# Empty data.table (0 rows and 19 cols): year,month,day,dep_time,sched_dep_time,dep_delay,arr_time,sched_arr_time,arr_delay,...
+```
+
+Comprender estos comportamientos puede ayudar a evitar confusiones al tratar con elementos inexistentes en sus datos.
+
+#### Símbolo especial `.N`: {#special-N}
+
+`.N` es una variable integrada especial que contiene el número de observaciones *en el grupo actual*. Es especialmente útil cuando se combina con `by`, como veremos en la siguiente sección. En ausencia de operaciones de agrupación por, simplemente devuelve el número de filas del subconjunto.
+
+Ahora que lo sabemos, podemos realizar la misma tarea utilizando `.N` de la siguiente manera:
+
+```{r}
+ans <- flights[origin == "JFK" & month == 6L, .N]
+ans
+```
+
+* Una vez más, filtramos en `i` para obtener los *índices de fila* donde el aeropuerto `origen` es igual a *"JFK"*, y el mes es igual a *6*.
+
+* Vemos que `j` usa solo `.N` y ninguna otra columna. Por lo tanto, no se materializa el subconjunto completo. Simplemente devolvemos el número de filas del subconjunto (que es simplemente la longitud de los índices de fila).
+
+* Tenga en cuenta que no envolvimos `.N` con `list()` ni `.()`. Por lo tanto, se devuelve un vector.
+
+Podríamos haber realizado la misma operación con `nrow(flights[origin == "JFK" & month == 6L])`. Sin embargo, primero tendría que filtrar la `data.table` completa correspondiente a los *índices de fila* en `i` *y luego* devolver las filas usando `nrow()`, lo cual es innecesario e ineficiente. Abordaremos este y otros aspectos de optimización en detalle en la viñeta de *diseño de `data.table`*.
+
+### h) ¡Genial! Pero ¿cómo puedo referirme a las columnas por sus nombres en `j` (como en un `data.frame`)? {#refer-j}
+
+Si escribe los nombres de las columnas explícitamente, no hay diferencia en comparación con un `data.frame` (desde v1.9.8).
+
+#### -- Seleccione las columnas `arr_delay` y `dep_delay` mediante el método `data.frame`.
+
+```{r j_cols_no_with}
+ans <- flights[, c("arr_delay", "dep_delay")]
+head(ans)
+```
+
+Si ha almacenado las columnas deseadas en un vector de caracteres, hay dos opciones: utilizar el prefijo `..` o utilizar el argumento `with`.
+
+#### -- Seleccionar columnas nombradas en una variable usando el prefijo `..`
+
+```{r j_cols_dot_prefix}
+select_cols = c("arr_delay", "dep_delay")
+flights[ , ..select_cols]
+```
+
+Para aquellos familiarizados con la terminal Unix, el prefijo `..` debería recordar al comando "up-one-level", que es análogo a lo que sucede aquí: las señales `..` a `data.table` para buscar la variable `select_cols` "up-one-level", es decir, dentro del entorno global en este caso.
+
+#### -- Seleccionar columnas nombradas en una variable usando `with = FALSE`
+
+```{r j_cols_with}
+flights[ , select_cols, with = FALSE]
+```
+
+El argumento se llama `with`, en honor a la función `with()` de R, debido a su funcionalidad similar. Supongamos que tiene un `data.frame` `DF` y desea filtrar todas las filas donde `x > 1`. En `base` de R, puede hacer lo siguiente:
+
+```{r}
+DF = data.frame(x = c(1,1,1,2,2,3,3,3), y = 1:8)
+
+## (1) normal way
+DF[DF$x > 1, ] # data.frame needs that ',' as well
+
+## (2) using with
+DF[with(DF, x > 1), ]
+```
+
+* El uso de `with()` en (2) permite usar la columna `x` de `DF` como si fuera una variable.
+
+ Hence, the argument name `with` in `data.table`. Setting `with = FALSE` disables the ability to refer to columns as if they are variables, thereby restoring the "`data.frame` mode".
+
+* También podemos deseleccionar columnas usando `-` o `!`. Por ejemplo:
+
+ ```r
+ ## not run
+
+ # returns all columns except arr_delay and dep_delay
+ ans <- flights[, !c("arr_delay", "dep_delay")]
+ # or
+ ans <- flights[, -c("arr_delay", "dep_delay")]
+ ```
+
+* Desde `v1.9.5+`, también podemos seleccionar especificando los nombres de las columnas de inicio y fin, por ejemplo, `año:día` para seleccionar las primeras tres columnas.
+
+ ```r
+ ## not run
+
+ # returns year,month and day
+ ans <- flights[, year:day]
+ # returns day, month and year
+ ans <- flights[, day:year]
+ # returns all columns except year, month and day
+ ans <- flights[, -(year:day)]
+ ans <- flights[, !(year:day)]
+ ```
+
+ This is particularly handy while working interactively.
+
+`with = TRUE` es el valor predeterminado en `data.table` porque podemos hacer mucho más al permitir que `j` maneje expresiones, especialmente cuando se combina con `by`, como veremos en un momento.
+
+## 2. Agregaciones
+
+Ya vimos `i` y `j` de la forma general de `data.table` en la sección anterior. En esta sección, veremos cómo se pueden combinar con `by` para realizar operaciones *por grupo*. Veamos algunos ejemplos.
+
+### a) Agrupación mediante `by`
+
+#### --¿Cómo podemos obtener el número de viajes correspondientes a cada aeropuerto de origen?
+
+```{r}
+ans <- flights[, .(.N), by = .(origin)]
+ans
+
+## or equivalently using a character vector in 'by'
+# ans <- flights[, .(.N), by = "origin"]
+```
+
+* Sabemos que `.N` [es una variable especial](#special-N) que contiene el número de filas del grupo actual. Al agrupar por `origen` se obtiene el número de filas, `.N`, de cada grupo.
+
+* Al ejecutar `head(flights)`, se puede ver que los aeropuertos de origen aparecen en el orden *"JFK"*, *"LGA"* y *"EWR"*. El orden original de agrupación de las variables se conserva en el resultado. *¡Es importante tener esto en cuenta!*
+
+* Dado que no proporcionamos un nombre para la columna devuelta en `j`, se la denominó `N` automáticamente al reconocer el símbolo especial `.N`.
+
+* `by` también acepta un vector de caracteres de nombres de columnas. Esto es especialmente útil para la programación; por ejemplo, al diseñar una función con las columnas de agrupación (en forma de un vector de caracteres) como argumento.
+
+* Cuando solo hay una columna o expresión a la que hacer referencia en `j` y `by`, podemos omitir la notación `.()`. Esto es puramente por conveniencia. Podríamos hacer lo siguiente:
+
+ ```{r}
+ ans <- flights[, .N, by = origin]
+ ans
+ ```
+
+ We'll use this convenient form wherever applicable hereafter.
+
+#### ¿Cómo podemos calcular el número de viajes por aeropuerto de origen para el código de aerolínea `"AA"`? {#origin-N}
+
+El código de aerolínea único `"AA"` corresponde a *American Airlines Inc.*
+
+```{r}
+ans <- flights[carrier == "AA", .N, by = origin]
+ans
+```
+
+* Primero obtenemos los índices de fila para la expresión `carrier == "AA"` de `i`.
+
+* Usando estos *índices de fila*, obtenemos el número de filas agrupadas por `origen`. Nuevamente, no se materializan columnas, ya que la expresión en `j` no requiere filtrar sobre ninguna columna y, por lo tanto, es rápida y eficiente en el uso de memoria.
+
+#### ¿Cómo podemos obtener el número total de viajes para cada par «origen-destino» para el código de operador «"AA"`? {#origin-dest-N}
+
+```{r}
+ans <- flights[carrier == "AA", .N, by = .(origin, dest)]
+head(ans)
+
+## or equivalently using a character vector in 'by'
+# ans <- flights[carrier == "AA", .N, by = c("origin", "dest")]
+```
+
+* `by` acepta varias columnas. Simplemente proporcionamos todas las columnas por las que se agrupará. Observe el uso de `.()` nuevamente en `by`; nuevamente, esto es solo una abreviatura de `list()`, y `list()` también se puede usar aquí. De nuevo, seguiremos usando `.()` en esta viñeta.
+
+#### ¿Cómo podemos obtener el retraso promedio de llegada y salida para cada par `orig,dest` para cada mes para el código de operador `"AA"`? {#origin-dest-month}
+
+```{r}
+ans <- flights[carrier == "AA",
+ .(mean(arr_delay), mean(dep_delay)),
+ by = .(origin, dest, month)]
+ans
+```
+
+* Dado que no proporcionamos nombres de columnas para las expresiones en `j`, se generaron automáticamente como `V1` y `V2`.
+
+* Una vez más, tenga en cuenta que el orden de entrada de las columnas de agrupación se conserva en el resultado.
+
+¿Y ahora qué pasa si queremos ordenar el resultado por las columnas de agrupación `origen`, `dest` y `mes`?
+
+### b) Ordenado `por`: `keyby`
+
+Que `data.table` conserve el orden original de los grupos es intencional y está diseñado así. En algunos casos, es esencial conservar el orden original. Sin embargo, a veces deseamos ordenar automáticamente según las variables de nuestra agrupación.
+
+#### --Entonces, ¿cómo podemos ordenar directamente por todas las variables de agrupación?
+
+```{r}
+ans <- flights[carrier == "AA",
+ .(mean(arr_delay), mean(dep_delay)),
+ keyby = .(origin, dest, month)]
+ans
+```
+
+* Solo cambiamos `by` por `keyby`. Esto ordena automáticamente el resultado según las variables de agrupación en orden creciente. De hecho, debido a que la implementación interna de `by` requiere primero una ordenación antes de recuperar el orden original de la tabla, `keyby` suele ser más rápido que `by` porque no requiere este segundo paso.
+
+Claves: En realidad, `keyby` hace algo más que simplemente ordenar. También establece una clave después de ordenar, estableciendo un atributo llamado `sorted`.
+
+Aprenderemos más sobre `claves` en la viñeta [`vignette("datatable-keys-fast-subset", package="data.table")`](datatable-keys-fast-subset.html); por ahora, todo lo que tienes que saber es que puedes usar `keyby` para ordenar automáticamente el resultado por las columnas especificadas en `by`.
+
+### c) Encadenamiento
+
+Reconsideremos la tarea de [obtener el número total de viajes para cada par `origen, destino` para el transportista *"AA"*](#origin-dest-N).
+
+```{r}
+ans <- flights[carrier == "AA", .N, by = .(origin, dest)]
+```
+
+#### -- ¿Cómo podemos ordenar 'ans' utilizando las columnas 'origin' en orden ascendente y 'dest' en orden descendente?
+
+Podemos almacenar el resultado intermedio en una variable y luego usar `order(origin, -dest)` en esa variable. Parece bastante sencillo.
+
+```{r}
+ans <- ans[order(origin, -dest)]
+head(ans)
+```
+
+* Recordemos que podemos usar `-` en una columna `character` en `order()` dentro del marco de `data.table`. Esto es posible gracias a la optimización de consultas interna de `data.table`.
+
+* Recuerde también que `order(...)` con el marco de un `data.table` se *optimiza automáticamente* para usar el orden de base rápido interno `forder()` de `data.table` para mayor velocidad.
+
+Pero esto requiere asignar el resultado intermedio y luego sobrescribirlo. Podemos mejorarlo y evitar por completo esta asignación intermedia a una variable temporal *encadenando* expresiones.
+
+```{r}
+ans <- flights[carrier == "AA", .N, by = .(origin, dest)][order(origin, -dest)]
+head(ans, 10)
+```
+
+* Podemos unir expresiones una tras otra, *formando una cadena* de operaciones, es decir, `DT[ ... ][ ... ][ ... ]`.
+
+* O también puedes encadenarlos verticalmente:
+
+ ```r
+ DT[ ...
+ ][ ...
+ ][ ...
+ ]
+ ```
+
+### d) Expresiones en `by`
+
+#### -- ¿`by` también puede aceptar *expresiones* o sólo toma columnas?
+
+Sí. Por ejemplo, si queremos saber cuántos vuelos salieron con retraso pero llegaron antes (o a tiempo), salieron y llegaron con retraso, etc.
+
+```{r}
+ans <- flights[, .N, .(dep_delay>0, arr_delay>0)]
+ans
+```
+
+* La última fila corresponde a `dep_delay > 0 = TRUE` y `arr_delay > 0 = FALSE`. Podemos ver que `r flights[!is.na(arr_delay) & !is.na(dep_delay), .N, .(dep_delay>0, arr_delay>0)][, N[4L]]` vuelos salieron tarde pero llegaron temprano (o a tiempo).
+
+* Tenga en cuenta que no le asignamos ningún nombre a `by-expression`. Por lo tanto, los nombres se asignaron automáticamente en el resultado. Al igual que con `j`, puede nombrar estas expresiones como lo haría con los elementos de cualquier `list`, por ejemplo, `DT[, .N, .(dep_delayed = dep_delay>0, arr_delayed = arr_delay>0)]`.
+
+* Puede proporcionar otras columnas junto con expresiones, por ejemplo: `DT[, .N, by = .(a, b>0)]`.
+
+### e) Varias columnas en `j` - `.SD`
+
+#### -- ¿Tenemos que calcular `mean()` para cada columna individualmente?
+
+Por supuesto, no es práctico tener que escribir `mean(myCol)` para cada columna, una por una. ¿Qué sucedería si se tuvieran 100 columnas para promediar `mean()`?
+
+¿Cómo podemos hacer esto de forma eficiente y concisa? Para ello, revise [este consejo](#tip-1): *"Siempre que la expresión `j` devuelva una `lista`, cada elemento de la `lista` se convertirá en una columna en la `data.table` resultante"*. Si podemos referirnos al *subconjunto de datos* de cada grupo como una variable *al agrupar*, podemos recorrer todas las columnas de esa variable usando la función base `lapply()`, ya conocida o que pronto conoceremos. No hay que aprender nuevos nombres específicos de `data.table`.
+
+#### Símbolo especial `.SD`: {#special-SD}
+
+`data.table` proporciona un símbolo *especial* llamado `.SD`. Significa **S**subset of **D**ata`. Es en sí mismo un `data.table` que contiene los datos del *grupo actual* definido mediante `by`.
+
+Recuerde que una `data.table` es internamente también una `lista` con todas sus columnas de igual longitud.
+
+Utilicemos la [`data.table` `DT` de antes](#what-is-datatable-1a) para tener una idea de cómo se ve `.SD`.
+
+```{r}
+DT
+
+DT[, print(.SD), by = ID]
+```
+
+* `.SD` contiene todas las columnas *excepto las columnas de agrupación* de forma predeterminada.
+
+* También se genera conservando el orden original: datos correspondientes a `ID = "b"`, luego `ID = "a"`, y luego `ID = "c"`.
+
+Para calcular en (múltiples) columnas, podemos simplemente usar la función base R `lapply()`.
+
+```{r}
+DT[, lapply(.SD, mean), by = ID]
+```
+
+* `.SD` contiene las filas correspondientes a las columnas `a`, `b` y `c` de ese grupo. Calculamos la `mean()` de cada una de estas columnas utilizando la función base `lapply()`, ya conocida.
+
+* Cada grupo devuelve una lista de tres elementos que contienen el valor medio que se convertirá en las columnas de la tabla `data.table` resultante.
+
+* Dado que `lapply()` devuelve una `lista`, no es necesario envolverla con un `.()` adicional (si es necesario, consulte [este consejo](#tip-1)).
+
+Ya casi terminamos. Queda un pequeño detalle por resolver. En nuestra tabla de datos `flights`, solo queríamos calcular la `mean()` de las columnas `arr_delay` y `dep_delay`. Sin embargo, `.SD` contendría todas las columnas excepto las variables de agrupación por defecto.
+
+#### -- ¿Cómo podemos especificar sólo las columnas en las que nos gustaría calcular la `media()`?
+
+#### .SDcols
+
+Usando el argumento `.SDcols`. Acepta nombres o índices de columna. Por ejemplo, `.SDcols = c("arr_delay", "dep_delay")` garantiza que `.SD` contenga solo estas dos columnas para cada grupo.
+
+Similar a [parte g)](#refer-j), también puede especificar las columnas que desea eliminar en lugar de las que desea conservar usando `-` o `!`. Además, puede seleccionar columnas consecutivas como `colA:colB` y deseleccionarlas como `!(colA:colB)` o `-(colA:colB)`.
+
+Ahora intentemos usar `.SD` junto con `.SDcols` para obtener la `media()` de las columnas `arr_delay` y `dep_delay` agrupadas por `origen`, `dest` y `mes`.
+
+```{r}
+flights[carrier == "AA", ## Only on trips with carrier "AA"
+ lapply(.SD, mean), ## compute the mean
+ by = .(origin, dest, month), ## for every 'origin,dest,month'
+ .SDcols = c("arr_delay", "dep_delay")] ## for just those specified in .SDcols
+```
+
+### f) Filtrar `.SD` para cada grupo:
+
+#### --¿Cómo podemos devolver las dos primeras filas de cada mes?
+
+```{r}
+ans <- flights[, head(.SD, 2), by = month]
+head(ans)
+```
+
+* `.SD` es una `data.table` que contiene todas las filas de *ese grupo*. Simplemente creamos un subconjunto de las dos primeras filas, como ya vimos [aquí](#subset-rows-integer).
+
+* Para cada grupo, `head(.SD, 2)` devuelve las primeras dos filas como una `data.table`, que también es una `lista`, por lo que no tenemos que envolverla con `.()`.
+
+### g) ¿Por qué mantener `j` tan flexible?
+
+Para mantener una sintaxis consistente y seguir usando funciones base ya existentes (y conocidas), en lugar de tener que aprender nuevas funciones. Para ilustrar, usemos el `data.table` `DT` que creamos al principio, en la sección [¿Qué es un data.table?](#what-is-datatable-1a).
+
+#### -- ¿Cómo podemos concatenar las columnas `a` y `b` para cada grupo en `ID`?
+
+```{r}
+DT[, .(val = c(a,b)), by = ID]
+```
+
+* Eso es todo. No se requiere sintaxis especial. Solo necesitamos saber la función base `c()`, que concatena vectores, y [la sugerencia anterior](#tip-1).
+
+#### --¿Qué sucede si queremos tener todos los valores de las columnas `a` y `b` concatenados, pero devueltos como una columna de lista?
+
+```{r}
+DT[, .(val = list(c(a,b))), by = ID]
+```
+
+* Aquí, primero concatenamos los valores con `c(a,b)` para cada grupo y los envolvemos con `list()`. Por lo tanto, para cada grupo, devolvemos una lista de todos los valores concatenados.
+
+* Tenga en cuenta que estas comas son solo para visualización. Una columna de lista puede contener cualquier objeto en cada celda; en este ejemplo, cada celda es un vector, y algunas celdas contienen vectores más largos que otras.
+
+Una vez que empiece a internalizar el uso de `j`, se dará cuenta de lo poderosa que puede ser la sintaxis. Una forma muy útil de comprenderla es experimentando con la ayuda de `print()`.
+
+Por ejemplo:
+
+```{r}
+## look at the difference between
+DT[, print(c(a,b)), by = ID] # (1)
+
+## and
+DT[, print(list(c(a,b))), by = ID] # (2)
+```
+
+```{r, echo = FALSE}
+p = function(x) paste0('', paste(deparse(substitute(x)), collapse = ' '), ' = ', x, '')
+```
+
+En (1), para cada grupo, se devuelve un vector, con longitud = 6,4,2. Sin embargo, (2) devuelve una lista de longitud 1 para cada grupo, cuyo primer elemento contiene vectores de longitud 6,4,2. Por lo tanto, (1) da como resultado una longitud de `{r} p(6+4+2)`, mientras que (2) devuelve `{r} p(1+1+1)`.
+
+La flexibilidad de j nos permite almacenar cualquier objeto de lista como elemento de data.table. Por ejemplo, cuando los modelos estadísticos se ajustan a grupos, estos modelos pueden almacenarse en una tabla data.table. El código es conciso y fácil de entender.
+
+```{r}
+## Do long distance flights cover up departure delay more than short distance flights?
+## Does cover up vary by month?
+flights[, `:=`(makeup = dep_delay - arr_delay)]
+
+makeup.models <- flights[, .(fit = list(lm(makeup ~ distance))), by = .(month)]
+makeup.models[, .(coefdist = coef(fit[[1]])[2], rsq = summary(fit[[1]])$r.squared), by = .(month)]
+```
+
+Usando data.frames, necesitamos un código más complicado para obtener el mismo resultado.
+
+```{r}
+setDF(flights)
+flights.split <- split(flights, f = flights$month)
+makeup.models.list <- lapply(flights.split, function(df) c(month = df$month[1], fit = list(lm(makeup ~ distance, data = df))))
+makeup.models.df <- do.call(rbind, makeup.models.list)
+data.frame(t(sapply(
+ makeup.models.df[, "fit"],
+ function(model) c(coefdist = coef(model)[2L], rsq = summary(model)$r.squared)
+)))
+setDT(flights)
+```
+
+## Resumen
+
+La forma general de la sintaxis de `data.table` es:
+
+```r
+DT[i, j, by]
+```
+
+Hemos visto hasta ahora que,
+
+#### Usando `i`:
+
+* Podemos filtrar filas de manera similar a un `data.frame`, excepto que no es necesario usar `DT$` repetidamente, ya que las columnas dentro del marco de un `data.table` se ven como si fueran *variables*.
+
+* También podemos ordenar una `data.table` usando `order()`, que internamente usa el orden rápido de data.table para un mejor rendimiento.
+
+Podemos hacer mucho más en `i` al introducir claves en `data.table`, lo que permite filtrados y uniones ultrarrápidos. Veremos esto en las viñetas [`vignette("datatable-keys-fast-subset", package="data.table")`](datatable-keys-fast-subset.html) y [`vignette("datatable-joins", package="data.table")`](datatable-joins.html).
+
+#### Usando `j`:
+
+1. Seleccione columnas a la manera de `data.table`: `DT[, .(colA, colB)]`.
+
+2. Seleccione columnas a la manera de `data.frame`: `DT[, c("colA", "colB")]`.
+
+3. Calcular en las columnas: `DT[, .(sum(colA), mean(colB))]`.
+
+4. Proporcione nombres si es necesario: `DT[, .(sA = suma(colA), mB = media(colB))]`.
+
+5. Combinar con `i`: `DT[colA > valor, suma(colB)]`.
+
+#### Usando `by`:
+
+* Usando `by`, podemos agrupar por columnas especificando una *lista de columnas*, un *vector de caracteres de nombres de columnas* o incluso *expresiones*. La flexibilidad de `j`, combinada con `by` e `i`, crea una sintaxis muy potente.
+
+* `by` puede manejar múltiples columnas y también *expresiones*.
+
+* Podemos agrupar columnas mediante `keyby` para ordenar automáticamente el resultado agrupado.
+
+* Podemos usar `.SD` y `.SDcols` en `j` para operar en múltiples columnas usando funciones base ya conocidas. Aquí hay algunos ejemplos:
+
+ 1. `DT[, lapply(.SD, fun), by = ..., .SDcols = ...]` - aplica `fun` a todas las columnas especificadas en `.SDcols` mientras agrupa por las columnas especificadas en `by`.
+
+ 2. `DT[, head(.SD, 2), by = ...]` - devuelve las dos primeras filas de cada grupo.
+
+ 3. `DT[col > val, head(.SD, 1), by = ...]` - combina `i` junto con `j` y `by`.
+
+#### Y recuerda el consejo:
+
+Siempre que `j` devuelva una `lista`, cada elemento de la lista se convertirá en una columna en la `data.table` resultante.
+
+Veremos cómo *agregar/actualizar/eliminar* columnas *por referencia* y cómo combinarlas con `i` y `by` en la [siguiente viñeta (`vignette("datatable-reference-semantics", package="data.table")`)](datatable-reference-semantics.html).
+
+***
+
+```{r, echo=FALSE}
+setDTthreads(.old.th)
+```
diff --git a/vignettes/es/datatable-joins.Rmd b/vignettes/es/datatable-joins.Rmd
new file mode 100644
index 000000000..e1830f258
--- /dev/null
+++ b/vignettes/es/datatable-joins.Rmd
@@ -0,0 +1,725 @@
+---
+title: "Uniones «join» en data.table"
+date: "`{r} Sys.Date()`"
+output:
+ litedown::html_format
+vignette: >
+ %\VignetteIndexEntry{Joins in data.table}
+ %\VignetteEngine{litedown::vignette}
+ \usepackage[utf8]{inputenc}
+editor_options:
+ chunk_output_type: console
+---
+
+```{r, echo = FALSE, message = FALSE}
+library(data.table)
+litedown::reactor(comment = "# ")
+```
+
+```{r, echo=FALSE, file='../_translation_links.R'}
+```
+
+`{r} .write.translation.links("Las traducciones de este documento están disponibles en: %s")`
+
+En esta viñeta aprenderá cómo realizar cualquier operación de *unión* (N. de T.: *join*) utilizando los recursos disponibles en la sintaxis de 'data.table'.
+
+Se presupone familiaridad con la sintaxis de `data.table`. De no ser así, lea las siguientes viñetas:
+
+- [`viñeta("datatable-intro", paquete="data.table")`](datatable-intro.html)
+- [`viñeta("datatable-reference-semantics", paquete="data.table")`](datatable-reference-semantics.html)
+- [`viñeta("datatable-keys-fast-subset", paquete="data.table")`](datatable-keys-fast-subset.html)
+
+***
+
+## 1. Definición de datos de ejemplo
+
+Para ilustrar cómo utilizar el método disponible con ejemplos de la vida real, simulemos una **base de datos normalizada** de un pequeño supermercado definiendo las siguientes tablas en una base de datos:
+
+1. «Productos», una tabla con filas que muestran las características de varios productos. Para mostrar cómo el framework gestiona los ***valores faltantes***, un id es `NA`.
+
+```{r, define_products}
+Products = rowwiseDT(
+ id=, name=, price=, unit=, type=,
+ 1L, "banana", 0.63, "unit", "natural",
+ 2L, "carrots", 0.89, "lb", "natural",
+ 3L, "popcorn", 2.99, "unit", "processed",
+ 4L, "soda", 1.49, "ounce", "processed",
+ NA, "toothpaste", 2.99, "unit", "processed"
+)
+```
+
+2. `NewTax`, una tabla con filas que definen algunos impuestos asociados a los productos procesados en función de sus unidades.
+
+```{r define_new_tax}
+NewTax = data.table(
+ unit = c("unit", "ounce"),
+ type = "processed",
+ tax_prop = c(0.65, 0.20)
+)
+
+NewTax
+```
+
+3. `ProductReceived`, una tabla con filas que simulan el inventario entrante semanal.
+
+```{r define_product_received}
+set.seed(2156)
+
+# NB: Jan 8, 2024 is a Monday.
+receipt_dates = seq(from=as.IDate("2024-01-08"), length.out=10L, by="week")
+
+ProductReceived = data.table(
+ id=1:10, # unique identifier for an supply transaction
+ date=receipt_dates,
+ product_id=sample(c(NA, 1:3, 6L), size=10L, replace=TRUE), # NB: product '6' is not recorded in Products above.
+ count=sample(c(50L, 100L, 150L), size=10L, replace=TRUE)
+)
+
+ProductReceived
+```
+
+4. `ProductSales`, una tabla con filas que simulan transacciones de clientes.
+
+```{r define_product_sales}
+set.seed(5415)
+
+# Monday-Friday (4 days later) for each of the weeks present in ProductReceived
+possible_weekdays <- as.IDate(sapply(receipt_dates, `+`, 0:4))
+
+ProductSales = data.table(
+ id = 1:10,
+ date = sort(sample(possible_weekdays, 10L)),
+ product_id = sample(c(1:3, 7L), size = 10L, replace = TRUE), # NB: product '7' is in neither Products nor ProductReceived.
+ count = sample(c(50L, 100L, 150L), size = 10L, replace = TRUE)
+)
+
+ProductSales
+```
+
+## 2. Sintaxis de uniones join de `data.table`
+
+Antes de aprovechar la sintaxis `data.table` para realizar operaciones de unión, necesitamos saber qué argumentos pueden ayudarnos a realizar uniones *join* exitosas.
+
+El siguiente diagrama muestra una descripción de cada argumento básico. En las siguientes secciones, mostraremos cómo usar cada uno y añadiremos complejidad gradualmente.
+
+```
+x[i, on, nomatch]
+| | | |
+| | | \__ If NULL only returns rows linked in x and i tables
+| | \____ a character vector or list defining match logic
+| \_____ primary data.table, list or data.frame
+\____ secondary data.table
+```
+
+**Nota**: Tenga en cuenta que el orden estándar de los argumentos en `data.table` es `dt[i, j, by]`. Para las operaciones de unión, se recomienda pasar los argumentos `on` y `nomatch` por nombre para evitar usar `j` y `by` cuando no sean necesarios.
+
+## 3. Equi se une
+
+Este es el caso más común y sencillo ya que podemos encontrar elementos comunes entre tablas para combinar.
+
+La relación entre tablas puede ser:
+
+- **Uno a uno**: Cuando cada valor coincidente es único en cada tabla.
+- **Uno a muchos**: Cuando algunos valores coincidentes se repiten en una de las tablas y ambos son únicos en la otra.
+- **Muchos a muchos**: Cuando los valores coincidentes se repiten varias veces en cada tabla.
+
+En la mayoría de los siguientes ejemplos realizaremos coincidencias de *uno a muchos*, pero también nos tomaremos el tiempo para explicar los recursos disponibles para realizar coincidencias de *muchos a muchos*.
+
+### 3.1. Unión derecha (Right Join)
+
+Utilice este método si necesita combinar columnas de 2 tablas según una o más referencias pero ***manteniendo todas las filas presentes en la tabla ubicada a la derecha (entre corchetes)***.
+
+En nuestro contexto de supermercado, podemos realizar una unión derecha para ver más detalles sobre los productos recibidos, ya que esta es una relación *uno a muchos* al pasar un vector al argumento `on`.
+
+```{r}
+Products[ProductReceived,
+ on = c(id = "product_id")]
+```
+
+Como muchas cosas han cambiado, vamos a explicar las nuevas características en los siguientes grupos:
+
+- **Nivel de columna**
+ - El *primer grupo* de columnas en la nueva `data.table` proviene de la tabla `x`.
+ - El *segundo grupo* de columnas en la nueva `data.table` proviene de la tabla `i`.
+ - Si la operación de unión presenta algún **conflicto de nombre** (ambas tablas tienen el mismo nombre de columna), el ***prefijo*** `i.` se agrega a los nombres de columna de la **tabla de la derecha** (tabla en la posición `i`).
+
+- **Nivel de fila**
+ - El `product_id` faltante presente en la tabla `ProductReceived` en la fila 1 se correspondió exitosamente con el `id` faltante de la tabla `Products`, por lo que los ***valores `NA` se tratan como cualquier otro valor***.
+ - Se conservaron todas las filas de la tabla `i`, incluyendo:
+*- Filas que no coinciden como la que tiene `product_id = 6`.
+*- Filas que repiten el mismo `product_id` varias veces.
+
+#### 3.1.1. Unirse mediante un argumento de lista
+
+Si está siguiendo la viñeta, es posible que haya descubierto que usamos un vector para definir las relaciones entre las tablas en el argumento `on`, lo cual es realmente útil si está **creando sus propias funciones**, pero otra alternativa es usar una **lista** para definir las columnas que coinciden.
+
+Para utilizar esta capacidad, tenemos 2 alternativas equivalentes:
+
+- Envolviendo las columnas relacionadas en la función base R `lista`.
+
+```r
+Products[ProductReceived,
+ on = list(id = product_id)]
+```
+
+- Envolviendo las columnas relacionadas en el alias `lista` `.`.
+
+```r
+Products[ProductReceived,
+ on = .(id = product_id)]
+```
+
+#### 3.1.2. Alternativas para definir el argumento `on`
+
+En todos los ejemplos anteriores, pasamos los nombres de las columnas que queremos que coincidan con el argumento `on`, pero `data.table` también tiene alternativas a esa sintaxis.
+
+- **Unión natural**: Selecciona las columnas para realizar la coincidencia según nombres de columna comunes. Para ilustrar este método, cambiemos la columna de la tabla "Productos" de "id" a "product_id" y usemos la palabra clave ".NATURAL".
+
+```{r}
+ProductsChangedName = setnames(copy(Products), "id", "product_id")
+ProductsChangedName
+
+ProductsChangedName[ProductReceived, on = .NATURAL]
+```
+
+- **Unión con clave**: selecciona las columnas para realizar la coincidencia en función de las columnas con clave, independientemente de sus nombres. Para ilustrar este método, necesitamos definir claves en el mismo orden para ambas tablas.
+
+```{r}
+ProductsKeyed = setkey(copy(Products), id)
+key(ProductsKeyed)
+
+ProductReceivedKeyed = setkey(copy(ProductReceived), product_id)
+key(ProductReceivedKeyed)
+
+ProductsKeyed[ProductReceivedKeyed]
+```
+
+#### 3.1.3. Operaciones posteriores a la incorporación
+
+La mayoría de las veces, tras unirnos, necesitamos realizar transformaciones adicionales. Para ello, tenemos las siguientes alternativas:
+
+- Encadenar una nueva instrucción añadiendo un par de corchetes `[]`.
+- Pasar una lista con las columnas que queremos conservar o crear al argumento `j`.
+
+Nuestra recomendación es utilizar la segunda alternativa si es posible, ya que es **más rápida** y utiliza **menos memoria** que la primera.
+
+##### Administrar nombres de columnas compartidas con el argumento j
+
+El argumento `j` ofrece excelentes alternativas para gestionar uniones con tablas que **comparten los mismos nombres para varias columnas**. Por defecto, todas las columnas toman su origen de la tabla `x`, pero también podemos usar el prefijo `x.` para especificar el origen y el prefijo `i.` para usar cualquier columna de la tabla declarada en el argumento `i` de la tabla `x`.
+
+Volviendo al pequeño supermercado, luego de actualizar la tabla `ProductReceived` con la tabla `Products`, supongamos que queremos aplicar los siguientes cambios:
+
+- Cambie los nombres de las columnas de `id` a `product_id` y de `i.id` a `received_id`.
+- Agregue `total_value`.
+
+```{r}
+Products[
+ ProductReceived,
+ on = c("id" = "product_id"),
+ j = .(product_id = x.id,
+ name = x.name,
+ price,
+ received_id = i.id,
+ date = i.date,
+ count,
+ total_value = price * count)
+]
+```
+
+##### Resumiendo con `on` en `data.table`
+
+También podemos utilizar esta alternativa para devolver resultados agregados en función de las columnas presentes en la tabla `x`.
+
+Por ejemplo, podríamos estar interesados en cuánto dinero gastamos comprando cada producto a lo largo de los días.
+
+```{r}
+dt1 = ProductReceived[
+ Products,
+ on = c("product_id" = "id"),
+ by = .EACHI,
+ j = .(total_value_received = sum(price * count))
+]
+
+# alternative using multiple [] queries
+dt2 = ProductReceived[
+ Products,
+ on = c("product_id" = "id"),
+][, .(total_value_received = sum(price * count)),
+ by = "product_id"
+]
+
+identical(dt1, dt2)
+```
+
+#### 3.1.4. Unión basada en varias columnas
+
+Hasta ahora solo hemos unido `data.table` en función de 1 columna, pero es importante saber que el paquete puede unir tablas que coincidan con varias columnas.
+
+Para ilustrar esto, supongamos que queremos agregar `tax_prop` de `NewTax` para **actualizar** la tabla `Products`.
+
+```{r}
+NewTax[Products, on = c("unit", "type")]
+```
+
+### 3.2. Inner join
+
+Utilice este método si necesita combinar columnas de 2 tablas según una o más referencias pero ***manteniendo solo las filas coincidentes en ambas tablas***.
+
+Para realizar esta operación solo necesitamos agregar `nomatch = NULL` a cualquiera de las operaciones de unión anteriores para devolver los mismos resultados.
+
+```{r}
+# First Table
+Products[ProductReceived,
+ on = c("id" = "product_id"),
+ nomatch = NULL]
+
+# Second Table
+ProductReceived[Products,
+ on = .(product_id = id),
+ nomatch = NULL]
+```
+
+A pesar de que ambas tablas tienen la misma información, existen algunas diferencias relevantes:
+
+- Presentan un orden de columnas diferente.
+- Tienen diferencias en el nombre de las columnas:
+ - La columna `id` en la primera tabla tiene la misma información que `product_id` en la segunda tabla.
+ - La columna `i.id` en la primera tabla tiene la misma información que `id` en la segunda tabla.
+
+### 3.3. Anti-unión
+
+Este método **conserva sólo las filas que no coinciden con ninguna fila de una segunda tabla**.
+
+Para aplicar esta técnica podemos negar (`!`) la tabla ubicada en el argumento `i`.
+
+```{r}
+Products[!ProductReceived,
+ on = c("id" = "product_id")]
+```
+
+Como puedes ver, el resultado solo tiene 'soda', ya que era el único producto que no estaba presente en la tabla 'ProductReceived'.
+
+```{r}
+ProductReceived[!Products,
+ on = c("product_id" = "id")]
+```
+
+En este caso, la operación devuelve la fila con `product_id = 6`, ya que no está presente en la tabla `Productos`.
+
+### 3.4. Semi unión
+
+Este método extrae **sólo las filas que coinciden con cualquier fila de una segunda tabla**, sin combinar las columnas de las tablas.
+
+Es muy similar a filtrar via un "join", pero como en esta ocasión estamos pasando una tabla completa en `i`, debemos asegurarnos de que:
+
+- Cualquier fila en la tabla `x` se duplica debido a la duplicación de filas en la tabla pasada al argumento `i`.
+
+- Todas las filas renombradas desde `x` deben mantener el orden de filas original.
+
+Para realizar esto puedes aplicar los siguientes pasos:
+
+1. Realice un **inner join** con `which = TRUE` para guardar los números de fila relacionados con cada fila coincidente de la tabla `x`.
+
+```{r}
+SubSetRows = Products[
+ ProductReceived,
+ on = .(id = product_id),
+ nomatch = NULL,
+ which = TRUE
+]
+
+SubSetRows
+```
+
+2. Seleccionar y ordenar los identificadores de filas únicos.
+
+```{r}
+SubSetRowsSorted = sort(unique(SubSetRows))
+
+SubSetRowsSorted
+```
+
+3. Seleccionar las `x` filas que se conservarán.
+
+```{r}
+Products[SubSetRowsSorted]
+```
+
+### 3.5. Unión izquierda (left join)
+
+Utilice este método si necesita combinar columnas de 2 tablas según una o más referencias pero ***manteniendo todas las filas presentes en la tabla ubicada a la izquierda***.
+
+Para realizar esta operación, solo necesitamos **intercambiar el orden entre ambas tablas** y los nombres de las columnas en el argumento `on`.
+
+```{r}
+ProductReceived[Products,
+ on = list(product_id = id)]
+```
+
+A continuación se presentan algunas consideraciones importantes:
+
+- **Nivel de columna**
+ - El *primer grupo* de columnas ahora proviene de la tabla `ProductReceived` ya que es la tabla `x`.
+ - El *segundo grupo* de columnas ahora proviene de la tabla `Products` ya que es la tabla `i`.
+ - No agregó el prefijo `i.` a ninguna columna.
+
+- **Nivel de fila**
+ - Se conservaron todas las filas de la tabla `i`: la entrada de soda de `Products` que no coincidió con ninguna fila en `ProductReceived` todavía es parte de los resultados.
+ - La fila relacionada con `product_id = 6` ya no es parte de los resultados porque no está presente en la tabla `Products`.
+
+#### 3.5.1. Unión tras operaciones de encadenamiento
+
+Una de las características clave de `data.table` es que podemos aplicar varias operaciones antes de guardar nuestros resultados finales encadenando corchetes.
+
+```r
+DT[
+ ...
+][
+ ...
+][
+ ...
+]
+```
+
+Hasta ahora, si después de aplicar todas esas operaciones **queremos unir nuevas columnas sin eliminar ninguna fila**, necesitaríamos detener el proceso de encadenamiento, guardar una tabla temporal y luego aplicar la operación de unión.
+
+Para evitar esa situación, podemos usar símbolos especiales `.SD`, para aplicar una **unión derecha (right join) basada en la tabla modificada**.
+
+```{r}
+NewTax[Products,
+ on = c("unit", "type")
+][, ProductReceived[.SD,
+ on = list(product_id = id)],
+ .SDcols = !c("unit", "type")]
+```
+
+### 3.6. Unión de muchos a muchos
+
+A veces queremos unir tablas en función de columnas con **valores `id` duplicados** para luego realizar algunas transformaciones más adelante.
+
+Para ilustrar esta situación tomemos como ejemplo el `product_id == 1L`, que tiene 4 filas en nuestra tabla `ProductReceived`.
+
+```{r}
+ProductReceived[product_id == 1L]
+```
+
+Y 4 filas en nuestra tabla 'ProductSales'.
+
+```{r}
+ProductSales[product_id == 1L]
+```
+
+Para realizar esta unión solo necesitamos filtrar `product_id == 1L` en la tabla `i` para limitar la unión solo a ese producto y establecer el argumento `allow.cartesian = TRUE` para permitir combinar cada fila de una tabla con cada fila de la otra tabla.
+
+```{r}
+ProductReceived[ProductSales[list(1L),
+ on = "product_id",
+ nomatch = NULL],
+ on = "product_id",
+ allow.cartesian = TRUE]
+```
+
+Una vez que entendemos el resultado, podemos aplicar el mismo proceso para **todos los productos**.
+
+```{r}
+ProductReceived[ProductSales,
+ on = "product_id",
+ allow.cartesian = TRUE]
+```
+
+**Nota**: El valor predeterminado de `allow.cartesian` es FALSE, ya que esto rara vez es lo que el usuario desea, y una combinación cruzada de este tipo puede generar un número muy elevado de filas en el resultado. Por ejemplo, si la Tabla A tiene 100 filas y la Tabla B tiene 50, su producto cartesiano resultaría en 5000 filas (100 * 50). Esto puede consumir rápidamente mucha memoria para conjuntos de datos grandes.
+
+#### 3.6.1. Seleccionar una coincidencia
+
+Tras unir la tabla, podríamos descubrir que solo necesitamos devolver una única unión para extraer la información necesaria. En este caso, tenemos dos alternativas:
+
+- Podemos seleccionar la **primera coincidencia**, representada en el siguiente ejemplo por `id = 2`.
+
+```{r}
+ProductReceived[ProductSales[product_id == 1L],
+ on = .(product_id),
+ allow.cartesian = TRUE,
+ mult = "first"]
+```
+
+- Podemos seleccionar la **última coincidencia**, representada en el siguiente ejemplo por `id = 9`.
+
+```{r}
+ProductReceived[ProductSales[product_id == 1L],
+ on = .(product_id),
+ allow.cartesian = TRUE,
+ mult = "last"]
+```
+
+#### 3.6.2. Unión cruzada
+
+Si desea obtener **todas las combinaciones de filas posibles** independientemente de cualquier columna de identificación en particular, podemos seguir el siguiente proceso:
+
+1. Crea una nueva columna en ambas tablas con una constante.
+
+```{r}
+ProductsTempId = copy(Products)[, temp_id := 1L]
+```
+
+2. Unir ambas tablas en función de la nueva columna y eliminarla después de finalizar el proceso, ya que no tiene motivos para permanecer después de unirse.
+
+```{r}
+AllProductsMix =
+ ProductsTempId[ProductsTempId,
+ on = "temp_id",
+ allow.cartesian = TRUE]
+
+AllProductsMix[, temp_id := NULL]
+
+# Removing type to make easier to see the result when printing the table
+AllProductsMix[, !c("type", "i.type")]
+```
+
+### 3.7. Unión completa
+
+Utilice este método si necesita combinar columnas de 2 tablas según una o más referencias ***sin eliminar ninguna fila***.
+
+Como vimos en la sección anterior, cualquiera de las operaciones anteriores puede mantener el `product_id = 6` faltante y el **soda** (`product_id = 4`) como parte de los resultados.
+
+Para evitar este problema, podemos utilizar la función `merge` aunque es más sencilla que utilizar la sintaxis de unión nativa `data.table`.
+
+```{r}
+merge(x = Products,
+ y = ProductReceived,
+ by.x = "id",
+ by.y = "product_id",
+ all = TRUE,
+ sort = FALSE)
+```
+
+## 4. Unión no equitativa
+
+Una unión no equitativa es un tipo de unión donde la condición para la coincidencia de filas se basa en operadores de comparación distintos de la igualdad, como `<`, `>`, `<=` o `>=`. Esto permite **criterios de unión más flexibles**. En `data.table`, las uniones no equitativas son particularmente útiles para operaciones como:
+
+- Encontrar la coincidencia más cercana.
+- Comparar rangos de valores entre tablas.
+
+Es una gran alternativa cuando, después de aplicar una unión derecha o interna, quieres:
+
+- Desea reducir la cantidad de filas devueltas en función de las comparaciones de columnas numéricas entre tablas.
+- No es necesario conservar las columnas de la tabla x *(la `data.table` secundaria)* en el resultado final.
+
+Para ilustrar cómo funciona esto, centrémonos en las ventas y recepciones del producto 2.
+
+```{r}
+ProductSalesProd2 = ProductSales[product_id == 2L]
+ProductReceivedProd2 = ProductReceived[product_id == 2L]
+```
+
+Si deseamos saber, por ejemplo, si podemos encontrar alguna recepción que haya tenido lugar antes de una fecha de venta, podemos aplicar lo siguiente.
+
+```{r}
+ProductReceivedProd2[ProductSalesProd2,
+ on = "product_id",
+ allow.cartesian = TRUE
+][date < i.date]
+```
+
+¿Qué sucede si simplemente aplicamos la misma lógica en la lista pasada a 'on'?
+
+- Como esta operación sigue siendo una unión derecha (right join), devuelve todas las filas de la tabla `i`, pero solo muestra los valores de `id` y `count` cuando se cumplen las reglas.
+
+- La fecha relacionada con `ProductReceivedProd2` se omitió de esta nueva tabla.
+
+```{r}
+ProductReceivedProd2[ProductSalesProd2,
+ on = list(product_id, date < date)]
+```
+
+Ahora, después de aplicar la unión, podemos limitar los resultados mostrando solo los casos que cumplen todos los criterios de unión.
+
+```{r}
+ProductReceivedProd2[ProductSalesProd2,
+ on = list(product_id, date < date),
+ nomatch = NULL]
+```
+
+### 4.1 Nombres de columnas de salida en uniones no equitativas
+
+Al realizar uniones no equitativas (`<`, `>`, `<=`, `>=`), los nombres de columna se asignan de la siguiente manera:
+
+- El operando izquierdo (columna `x`) determina el nombre de la columna en el resultado.
+- El operando derecho (columna `i`) aporta valores pero no conserva su nombre original.
+- De manera predeterminada, `data.table` no conserva la columna `i` utilizada en la condición de unión a menos que se solicite explícitamente.
+
+En uniones no equitativas, el lado izquierdo del operador (por ejemplo, `x_int` en `x_int >= i_int`) debe ser una columna de `x`, mientras que el lado derecho (por ejemplo, `i_int`) debe ser una columna de `i`.
+
+Las uniones no equitativas actualmente no admiten expresiones arbitrarias (pero consulte [#1639](https://github.com/Rdatatable/data.table/issues/1639)). Por ejemplo, `on = .(x_int >= i_int)` es válido, pero `on = .(x_int >= i_int + 1L)` no lo es. Para realizar una unión no equitativa de este tipo, primero agregue la expresión como una nueva columna, por ejemplo, `i[, i_int_plus_one := i_int + 1L]`, luego ejecute `.on(x_int >= i_int_plus_one)`.
+
+```{r non_equi_join_example}
+x <- data.table(x_int = 2:4, lower = letters[1:3])
+i <- data.table(i_int = c(2L, 4L, 5L), UPPER = LETTERS[1:3])
+x[i, on = .(x_int >= i_int)]
+```
+
+Conclusiones clave:
+
+- El nombre de la columna de salida (`x_int`) proviene de `x`, pero los valores provienen de `i_int` en `i`.
+- La última fila contiene `NA` porque ninguna fila en `x` coincide con la última fila en `i` (`UPPER == "C"`).
+- Se devuelven varias filas en `x` para que coincidan con la primera fila en `i` con `UPPER == "A"`.
+
+Si desea conservar la columna `i_int` de `i`, debe seleccionarla explícitamente en el resultado:
+
+```{r retain_i_column}
+x[i, on = .(x_int >= i_int), .(i_int = i.i_int, x_int = x.x_int, lower, UPPER)]
+```
+
+El uso de prefijos (`x.` e `i.`) no es estrictamente necesario en este caso ya que los nombres no son ambiguos, pero su uso garantiza que la salida distinga claramente `i_int` (de `i`) y `x_int` (de `x`).
+
+Si desea excluir filas no coincidentes (una *unión interna*), utilice `nomatch = NULL`:
+
+```{r retain_i_column_inner_join}
+x[i, on = .(x_int >= i_int), .(i_int = i.i_int, x_int = x.x_int, lower, UPPER), nomatch = NULL]
+```
+
+## 5. Unión rodante
+
+Las uniones continuas son especialmente útiles en el análisis de datos de series temporales. Permiten **emparejar filas según el valor más cercano** en una columna ordenada, generalmente una columna de fecha u hora.
+
+Esto es útil cuando necesita alinear datos de diferentes fuentes **que pueden no tener marcas de tiempo exactamente coincidentes**, o cuando desea transferir el valor más reciente.
+
+Por ejemplo, en datos financieros, puede utilizar una unión continua para asignar el precio de acción más reciente a cada transacción, incluso si las actualizaciones de precios y las transacciones no ocurren exactamente en el mismo momento.
+
+En nuestro ejemplo de supermercado, podemos utilizar una unión continua para hacer coincidir las ventas con la información más reciente del producto.
+
+Supongamos que el precio de los plátanos y las zanahorias cambia el primer día de cada mes.
+
+```{r}
+ProductPriceHistory = data.table(
+ product_id = rep(1:2, each = 3),
+ date = rep(as.IDate(c("2024-01-01", "2024-02-01", "2024-03-01")), 2),
+ price = c(0.59, 0.63, 0.65, # Banana prices
+ 0.79, 0.89, 0.99) # Carrot prices
+)
+
+ProductPriceHistory
+```
+
+Ahora, podemos realizar un *right join* dando un precio diferente para cada producto en función de la fecha de venta.
+
+```{r}
+ProductPriceHistory[ProductSales,
+ on = .(product_id, date),
+ roll = TRUE,
+ j = .(product_id, date, count, price)]
+```
+
+Si solo queremos ver los casos coincidentes, simplemente necesitamos agregar el argumento `nomatch = NULL` para realizar una unión interna.
+
+```{r}
+ProductPriceHistory[ProductSales,
+ on = .(product_id, date),
+ roll = TRUE,
+ nomatch = NULL,
+ j = .(product_id, date, count, price)]
+```
+
+## 6. Aprovechar la velocidad de incorporación
+
+### 6.1. Filtrado mediante joins
+
+Como vimos en la sección anterior, la tabla `x` se filtra según los valores disponibles en la tabla `i`. Este proceso es más rápido que pasar una expresión booleana al argumento `i`.
+
+Para filtrar la tabla `x` rápidamente no necesitamos pasar un `data.table` completo, podemos pasar una `list()` de vectores con los valores que queremos mantener u omitir de la tabla original.
+
+Por ejemplo, para filtrar las fechas donde el mercado recibió 100 unidades de plátanos (`product_id = 1`) o palomitas de maíz (`product_id = 3`) podemos utilizar lo siguiente:
+
+```{r}
+ProductReceived[list(c(1L, 3L), 100L),
+ on = c("product_id", "count")]
+```
+
+Como al final filtramos según una operación de unión, el código devolvió una fila que no estaba presente en la tabla original. Para evitar este comportamiento, se recomienda agregar siempre el argumento `nomatch = NULL`.
+
+```{r}
+ProductReceived[list(c(1L, 3L), 100L),
+ on = c("product_id", "count"),
+ nomatch = NULL]
+```
+
+También podemos usar esta técnica para filtrar cualquier combinación de valores, prefijándolos con `!` para negar la expresión en el argumento `i` y manteniendo `nomatch` con su valor predeterminado. Por ejemplo, podemos filtrar las dos filas que filtramos anteriormente.
+
+```{r}
+ProductReceived[!list(c(1L, 3L), 100L),
+ on = c("product_id", "count")]
+```
+
+Si solo desea filtrar un valor para una sola **columna de caracteres**, puede omitir la llamada a la función `list()` y pasar el valor a filtrar en el argumento `i`.
+
+```{r}
+Products[c("banana","popcorn"),
+ on = "name",
+ nomatch = NULL]
+
+Products[!"popcorn",
+ on = "name"]
+```
+
+### 6.2. Actualización por referencia
+
+Utilice `:=` para modificar columnas **por referencia** (sin copia) durante las uniones. Sintaxis general: `x[i, on=, (cols) := val]`.
+
+**Actualización simple uno a uno**
+
+Actualizar `Productos` con precios de `ProductPriceHistory`:
+
+```{r}
+Products[ProductPriceHistory,
+ on = .(id = product_id),
+ price := i.price]
+
+Products
+```
+
+- `i.price` hace referencia al precio de `ProductPriceHistory`.
+- Modifica `Products` en el lugar.
+
+**Actualizaciones agrupadas con `.EACHI`**
+
+Obtenga el último precio/fecha de cada producto:
+
+```{r Updating_with_the_Latest_Record}
+Products[ProductPriceHistory,
+ on = .(id = product_id),
+ `:=`(price = last(i.price), last_updated = last(i.date)),
+ by = .EACHI]
+
+Products
+```
+
+- `by = .EACHI` agrupa por filas en `i` (1 grupo por fila de ProductPriceHistory).
+- `last()` devuelve el último valor
+
+**Actualización eficiente mediante Right Join**
+
+Agregue detalles del producto a `ProductPriceHistory` sin copiar:
+
+```{r}
+cols <- setdiff(names(Products), "id")
+ProductPriceHistory[, (cols) :=
+ Products[.SD, on = .(id = product_id), .SD, .SDcols = cols]]
+setnafill(ProductPriceHistory, fill=0, cols="price") # Handle missing values
+
+ProductPriceHistory
+```
+
+- En `i`, `.SD` hace referencia a `ProductPriceHistory`.
+- En `j`, `.SD` hace referencia a `Products`.
+- `:=` y `setnafill()` actualizan `ProductPriceHistory` por referencia.
+
+## Referencia
+
+- *Entendiendo las uniones continuas en data.table*: https://www.r-bloggers.com/2016/06/understanding-data-table-rolling-joins/
+
+- *Semi-unión con data.table*: https://stackoverflow.com/questions/18969420/perform-a-semi-join-with-data-table
+
+- *Unión cruzada con data.table*: https://stackoverflow.com/questions/10600060/how-to-do-cross-join-in-r
+
+- *¿Cómo se hace una unión completa usando data.table?*: https://stackoverflow.com/questions/15170741/how-does-one-do-a-full-join-using-data-table
+
+- *Data.frame mejorado*: https://rdatatable.gitlab.io/data.table/reference/data.table.html
diff --git a/vignettes/es/datatable-keys-fast-subset.Rmd b/vignettes/es/datatable-keys-fast-subset.Rmd
new file mode 100644
index 000000000..007e9ba85
--- /dev/null
+++ b/vignettes/es/datatable-keys-fast-subset.Rmd
@@ -0,0 +1,500 @@
+---
+title: "Claves y filtrado rápido con búsqueda binaria"
+date: "`{r} Sys.Date()`"
+output:
+ litedown::html_format
+vignette: >
+ %\VignetteIndexEntry{Keys and fast binary search based subset}
+ %\VignetteEngine{litedown::vignette}
+ \usepackage[utf8]{inputenc}
+---
+
+```{r, echo=FALSE, file='../_translation_links.R'}
+```
+
+`{r} .write.translation.links("Las traducciones de este documento están disponibles en: %s")`
+
+```{r, echo = FALSE, message = FALSE}
+library(data.table)
+litedown::reactor(comment = "# ")
+.old.th = setDTthreads(1)
+```
+
+Esta viñeta está dirigida a quienes ya están familiarizados con la sintaxis de *data.table*, su forma general, cómo filtrar [N. del T.: *subset*] filas en `i`, seleccionar y calcular columnas, agregar, modificar y eliminar columnas *por referencia* en `j` y agrupar mediante `by`. Si no está familiarizado con estos conceptos, lea primero las viñetas [`vignette("datatable-intro", package="data.table")`](datatable-intro.html) y [`vignette("datatable-reference-semantics", package="data.table")`](datatable-reference-semantics.html).
+
+***
+
+## Datos {#data}
+
+Usaremos los mismos datos de `flights` que en la viñeta [`vignette("datatable-intro", package="data.table")`](datatable-intro.html).
+
+```{r, echo = FALSE}
+options(width = 100L)
+```
+
+```{r}
+flights <- fread("../flights14.csv")
+head(flights)
+dim(flights)
+```
+
+## Introducción
+
+En esta viñeta,
+
+* primero introduciremos el concepto de *clave* en *data.table*, y estableceremos y usaremos claves para efectuar un filtro en `i` basado en *búsquedas binarias rápidas*,
+
+* ver que podemos combinar filtros basados en clave junto con `j` y `by` exactamente de la misma manera que antes,
+
+* ver otros argumentos útiles adicionales: `mult` y `nomatch`,
+
+* y finalmente concluir mirando la ventaja de establecer claves: realizar *filtros basados en búsquedas binarias rápidas* y comparar con el enfoque de escaneo vectorial tradicional.
+
+## 1. Claves
+
+### a) ¿Qué es una *clave*?
+
+En la viñeta [`vignette("datatable-intro", package="data.table")`](datatable-intro.html), vimos cómo aplicar filtros en `i` usando expresiones lógicas, números de fila y el uso de `order()`. En esta sección, veremos otra forma de crear filtros increíblemente rápido: usando *claves*
+
+Pero primero, veamos los *data.frames*. Todos los *data.frames* tienen un atributo de nombre de fila. Considere el *data.frame* `DF` a continuación.
+
+```{r}
+set.seed(1L)
+DF = data.frame(ID1 = sample(letters[1:2], 10, TRUE),
+ ID2 = sample(1:3, 10, TRUE),
+ val = sample(10),
+ stringsAsFactors = FALSE,
+ row.names = sample(LETTERS[1:10]))
+DF
+
+rownames(DF)
+```
+
+Podemos filtrar una fila particular usando su nombre de fila como se muestra a continuación:
+
+```{r}
+DF["C", ]
+```
+
+Es decir, los nombres de fila son más o menos *un índice* de las filas de un *data.frame*. Sin embargo,
+
+1. Cada fila está limitada a *exactamente un* nombre de fila.
+
+ But, a person (for example) has at least two names - a *first* and a *second* name. It is useful to organise a telephone directory by *surname* then *first name*.
+
+2. Y los nombres de las filas deben ser *únicos*.
+
+ ```r
+ rownames(DF) = sample(LETTERS[1:5], 10, TRUE)
+ # Warning: non-unique values when setting 'row.names': 'C', 'D'
+ # Error in `.rowNamesDF<-`(x, value = value): duplicate 'row.names' are not allowed
+ ```
+
+Ahora vamos a convertirlo en una *data.table*.
+
+```{r}
+DT = as.data.table(DF)
+DT
+
+rownames(DT)
+```
+
+* Tenga en cuenta que los nombres de las filas se han restablecido.
+
+* *data.tables* nunca usa nombres de fila. Dado que *data.tables* **hereda** de *data.frames*, aún conserva el atributo de nombres de fila. Pero nunca los usa. Veremos por qué en breve.
+
+ If you would like to preserve the row names, use `keep.rownames = TRUE` in `as.data.table()` - this will create a new column called `rn` and assign row names to this column.
+
+En cambio, en *data.tables*, establecemos y usamos `keys` (claves). Piense en una `key` (clave) como **nombres de fila supercargados**.
+
+#### Claves y sus propiedades {#key-properties}
+
+1. Podemos establecer claves en *varias columnas* y la columna puede ser de *diferentes tipos*: *entero*, *numérico*, *carácter*, *factor*, *integer64*, etc. Los tipos *lista* y *complejos* aún no son compatibles.
+
+2. No se exige unicidad, es decir, se permiten valores de clave duplicados. Dado que las filas se ordenan por clave, cualquier valor duplicado en las columnas de clave aparecerá consecutivamente.
+
+3. Establecer una clave hace *dos* cosas:
+
+ a. physically reorders the rows of the *data.table* by the column(s) provided *by reference*, always in *increasing* order.
+
+ b. marks those columns as *key* columns by setting an attribute called `sorted` to the *data.table*.
+
+ Since the rows are reordered, a *data.table* can have at most one key because it can not be sorted in more than one way.
+
+Para el resto de la viñeta, trabajaremos con el conjunto de datos `flights`.
+
+### b) Establecer, obtener y usar claves en una *data.table*
+
+#### -- ¿Cómo podemos establecer la columna `origen` como clave en la *data.table* `flights`?
+
+```{r}
+setkey(flights, origin)
+head(flights)
+
+## alternatively we can provide character vectors to the function 'setkeyv()'
+# setkeyv(flights, "origin") # useful to program with
+```
+
+* Puede usar la función `setkey()` y proporcionar los nombres de las columnas (sin comillas). Esto es útil durante el uso interactivo.
+
+* También puede pasar un vector de caracteres de nombres de columnas a la función `setkeyv()`. Esto es especialmente útil al diseñar funciones que pasan columnas a las que se les asigna una clave como argumentos.
+
+* Tenga en cuenta que no tuvimos que asignar el resultado a una variable. Esto se debe a que, al igual que la función `:=` que vimos en la viñeta [`vignette("datatable-reference-semantics", package="data.table")`](datatable-reference-semantics.html), `setkey()` y `setkeyv()` modifican la entrada *data.table* *por referencia*. Devuelven el resultado de forma invisible.
+
+* La *data.table* ahora se reordena según la columna proporcionada: `origin`. Al reordenar por referencia, solo necesitamos memoria adicional de una columna con una longitud igual al número de filas de la *data.table*, lo que la hace muy eficiente en el uso de memoria.
+
+* También puede establecer claves directamente al crear *data.tables* mediante la función `data.table()` con el argumento `key`. Esta función acepta un vector de caracteres de nombres de columna.
+
+#### set* y `:=`:
+
+En *data.table*, el operador `:=` y todas las funciones `set*` (por ejemplo, `setkey`, `setorder`, `setnames`, etc.) son las únicas que modifican el objeto de entrada *por referencia*
+
+Una vez que se *clasifica* una *data.table* por ciertas columnas, se puede filtrar consultando esas columnas clave usando la notación `.()` en `i`. Recuerde que `.()` es un *alias* de `list()`.
+
+#### -- Usar la columna clave `origin` para filtrar por todas las filas donde el aeropuerto de origen coincida con *"JFK"*
+
+```{r}
+flights[.("JFK")]
+
+## alternatively
+# flights[J("JFK")] (or)
+# flights[list("JFK")]
+```
+
+* La columna *key* ya está configurada como `origin`. Por lo tanto, basta con proporcionar el valor, en este caso *"JFK"*, directamente. La sintaxis `.()` ayuda a identificar que la tarea requiere buscar el valor *"JFK"* en la columna key de *data.table* (en este caso, la columna `origin` de `flights` *data.table*).
+
+* Primero se obtienen los *índices de fila* correspondientes al valor *"JFK"* en `origin`. Y como no hay expresión en `j`, se devuelven todas las columnas correspondientes a esos índices de fila.
+
+* En una clave de columna única de tipo *carácter*, puede eliminar la notación `.()` y usar los valores directamente al filtrar, como filtrar usando nombres de filas en *data.frames*.
+
+ ```r
+ flights["JFK"] ## same as flights[.("JFK")]
+ ```
+
+* Podemos filtrar cualquier cantidad de valores que sea necesaria
+
+ ```r
+ flights[c("JFK", "LGA")] ## same as flights[.(c("JFK", "LGA"))]
+ ```
+
+ This returns all columns corresponding to those rows where `origin` column matches either *"JFK"* or *"LGA"*.
+
+#### -- ¿Cómo podemos obtener las columnas por las que se codifica una *data.table*?
+
+Usando la función `key()`.
+
+```{r}
+key(flights)
+```
+
+* Devuelve un vector de caracteres de todas las columnas clave.
+
+* Si no se establece ninguna clave, devuelve `NULL`.
+
+### c) Claves y columnas múltiples
+
+Para refrescar, las *claves* son como nombres de fila *sobrecargados*. Podemos establecer claves en varias columnas y pueden ser de varios tipos.
+
+#### -- ¿Cómo puedo configurar claves en las columnas `origin` *y* `dest`?
+
+```{r}
+setkey(flights, origin, dest)
+head(flights)
+
+## or alternatively
+# setkeyv(flights, c("origin", "dest")) # provide a character vector of column names
+
+key(flights)
+```
+
+* Ordena la *data.table* primero por la columna `origen` y luego por `dest` *por referencia*.
+
+#### -- Filtrar todas las filas utilizando columnas clave donde la primera columna clave `origin` coincide con *"JFK"* y la segunda columna clave `dest` coincide con *"MIA"*
+
+```{r}
+flights[.("JFK", "MIA")]
+```
+
+#### ¿Cómo funciona el filtrado aquí? {#multiple-key-point}
+
+* Es importante comprender cómo funciona esto internamente. *"JFK"* se compara primero con la primera columna clave `origin`. Y *dentro de esas filas coincidentes*, *"MIA"* se compara con la segunda columna clave `dest` para obtener *índices de fila* donde tanto `origin` como `dest` coinciden con los valores dados.
+
+* Dado que no se proporciona `j`, simplemente devolvemos *todas las columnas* correspondientes a esos índices de fila.
+
+#### -- Filtrar todas las filas donde solo la primera columna de clave `origin` coincide con *"JFK"*
+
+```{r}
+key(flights)
+
+flights[.("JFK")] ## or in this case simply flights["JFK"], for convenience
+```
+
+* Dado que no proporcionamos ningún valor para la segunda columna de clave `dest`, simplemente compara *"JFK"* con la primera columna de clave `origin` y devuelve todas las filas coincidentes.
+
+#### -- Filtrar todas las filas donde sólo la segunda columna clave `dest` coincide con *"MIA"*
+
+```{r}
+flights[.(unique(origin), "MIA")]
+```
+
+#### ¿Qué está pasando aquí?
+
+* Lea [esto](#multiple-key-point) de nuevo. El valor proporcionado para la segunda columna de clave *"MIA"* tiene que encontrar los valores coincidentes en la columna de clave `dest` *en las filas coincidentes proporcionadas por la primera columna de clave `origin`*. No podemos omitir los valores de las columnas de clave *anteriores*. Por lo tanto, proporcionamos *todos* los valores únicos de la columna de clave `origin`.
+
+* *"MIA"* se recicla automáticamente para ajustarse a la longitud de `unique(origin)` que es *3*.
+
+## 2. Combinar claves con `j` y `by`
+
+Hasta ahora, todo lo que hemos visto es el mismo concepto: obtener *índices de fila* en `i`, pero con un método diferente: usar `keys`. No debería sorprender que podamos hacer exactamente lo mismo en `j` y `by`, como se vio en los ejemplos anteriores. Lo ilustraremos con algunos ejemplos.
+
+### a) Seleccionar en `j`
+
+#### -- Devuelve la columna `arr_delay` como una *data.table* correspondiente a `origin = "LGA"` y `dest = "TPA"`.
+
+```{r}
+key(flights)
+flights[.("LGA", "TPA"), .(arr_delay)]
+```
+
+* Los *índices de fila* correspondientes a `origin == "LGA"` y `dest == "TPA"` se obtienen utilizando un *filtro basado en clave*.
+
+* Una vez que tenemos los índices de fila, revisamos `j`, que solo requiere la columna `arr_delay`. Así que simplemente seleccionamos la columna `arr_delay` para esos *índices de fila* de la misma manera que vimos en la viñeta [`vignette("datatable-intro", package="data.table")`](datatable-intro.html).
+
+* Podríamos haber devuelto el resultado usando `with = FALSE` también.
+
+ ```r
+ flights[.("LGA", "TPA"), "arr_delay", with = FALSE]
+ ```
+
+### b) Encadenamiento
+
+#### -- Con el resultado obtenido anteriormente, utilizar encadenamiento para ordenar la columna en orden decreciente
+
+```{r}
+flights[.("LGA", "TPA"), .(arr_delay)][order(-arr_delay)]
+```
+
+### c) Calcular o *hacer* en `j`
+
+#### -- Encontrar el retraso máximo de llegada correspondiente a `origin = "LGA"` y `dest = "TPA"`.
+
+```{r}
+flights[.("LGA", "TPA"), max(arr_delay)]
+```
+
+*Podemos verificar que el resultado es idéntico al primer valor (486) del ejemplo anterior.
+
+### d) *sub-asignar* por referencia usando `:=` en `j`
+
+Ya vimos este ejemplo en la viñeta [`vignette("datatable-reference-semantics", package="data.table")`](datatable-reference-semantics.html). Veamos todas las `horas` disponibles en la *data.table* `flights`:
+
+```{r}
+# get all 'hours' in flights
+flights[, sort(unique(hour))]
+```
+
+Observamos que hay un total de 25 valores únicos en los datos. Parece que hay tanto *0* como *24* horas. Reemplacemos *24* por *0*, pero esta vez usando *key*.
+
+```{r}
+setkey(flights, hour)
+key(flights)
+flights[.(24), hour := 0L]
+key(flights)
+```
+
+* Primero configuramos `key` como `hour`. Esto reordena los `flights` según la columna `hour` y marca esa columna como `key`.
+
+* Ahora podemos filtrar en `hour` usando la notación `.()`. Filtramos para el valor *24* y obtenemos los *índices de fila* correspondientes.
+
+* Y en esos índices de fila, reemplazamos la columna `key` con el valor `0`.
+
+* Dado que reemplazamos los valores en la columna *key*, la tabla de datos `flights` ya no se ordena por `hour`. Por lo tanto, la clave se ha eliminado automáticamente al establecerla en NULL.
+
+Ahora, no debería haber ningún *24* en la columna "hora".
+
+```{r}
+flights[, sort(unique(hour))]
+```
+
+### e) Agregación utilizando `by`
+
+Primero, establezcamos nuevamente la clave en `origin, dest`.
+
+```{r}
+setkey(flights, origin, dest)
+key(flights)
+```
+
+#### Obtener el retraso máximo de salida para cada mes correspondiente a `origin = "JFK"`. Ordenar el resultado por mes.
+
+```{r}
+ans <- flights["JFK", max(dep_delay), keyby = month]
+head(ans)
+key(ans)
+```
+
+* Filtramos en la columna `clave` *origen* para obtener los *índices de fila* correspondientes a *"JFK"*.
+
+* Una vez que obtenemos los índices de fila, solo necesitamos dos columnas: `month` para agrupar y `dep_delay` para obtener `max()` para cada grupo. Por lo tanto, la optimización de consulta de *data.table* filtra solo aquellas dos columnas correspondientes a los *índices de fila* obtenidos en `i`, para mayor velocidad y eficiencia de memoria.
+
+* Y en ese filtro, agrupamos por *mes* y calculamos `max(dep_delay)`.
+
+* Usamos `keyby` para clasificar automáticamente ese resultado por *mes*. Ahora entendemos lo que significa. Además de ordenar, también establece *mes* como la columna `key`.
+
+## 3. Argumentos adicionales: `mult` y `nomatch`
+
+### a) El argumento *mult*
+
+Podemos elegir, para cada consulta, si se deben devolver *todas* ("all") las filas coincidentes, o solo la *primera* ("first") o la *última* ("last") mediante el argumento `mult`. El valor predeterminado es *"all"*, el que hemos visto hasta ahora.
+
+#### -- Obtener solo la primera fila coincidente de todas las filas donde `origin` coincide con *"JFK"* y `dest` coincide con *"MIA"*
+
+```{r}
+flights[.("JFK", "MIA"), mult = "first"]
+```
+
+#### -- Filtrar solo la última fila coincidente de todas las filas donde `origin` coincide con *"LGA", "JFK", "EWR"* y `dest` coincide con *"XNA"*
+
+```{r}
+flights[.(c("LGA", "JFK", "EWR"), "XNA"), mult = "last"]
+```
+
+* La consulta *"JFK", "XNA"* no coincide con ninguna fila en `flights` y, por lo tanto, devuelve `NA`.
+
+* Una vez más, la consulta para la segunda columna de clave `dest`, *"XNA"*, se recicla para ajustarse a la longitud de la consulta para la primera columna de clave `origin`, que tiene una longitud 3.
+
+### b) El argumento *nomatch*
+
+Podemos elegir si las consultas que no coinciden deben devolver "NA" o ignorarse por completo utilizando el argumento "nomatch".
+
+#### -- Del ejemplo anterior, filtrar de todas las filas solo si hay una coincidencia
+
+```{r}
+flights[.(c("LGA", "JFK", "EWR"), "XNA"), mult = "last", nomatch = NULL]
+```
+
+* El valor predeterminado para `nomatch` es `NA`. Si se establece `nomatch = NULL`, se omiten las consultas sin coincidencias.
+
+* La consulta “JFK”, “XNA” no coincide con ninguna fila en vuelos y, por lo tanto, se omite.
+
+## 4. Búsqueda binaria vs. escaneo vectorial
+
+Hemos visto hasta ahora cómo podemos establecer y usar claves para crear filtros. Pero ¿cuál es la ventaja? Por ejemplo, en lugar de hacer:
+
+```r
+# key by origin,dest columns
+flights[.("JFK", "MIA")]
+```
+
+Podríamos haber hecho:
+
+```r
+flights[origin == "JFK" & dest == "MIA"]
+```
+
+Una de las ventajas más probables es su sintaxis más corta. Pero aún más, los *filtros basados en búsqueda binaria* son **increíblemente rápidos**.
+
+Con el tiempo, `data.table` se optimiza y actualmente la última llamada se optimiza automáticamente para usar la búsqueda binaria. Para usar el escaneo vectorial lento, es necesario eliminar la clave.
+
+```r
+setkey(flights, NULL)
+flights[origin == "JFK" & dest == "MIA"]
+```
+
+### a) Rendimiento del enfoque de búsqueda binaria
+
+Para ilustrarlo, creemos una *data.table* de muestra con 20 millones de filas y tres columnas y clasifiquémosla por las columnas `x` e `y`.
+
+```{r}
+set.seed(2L)
+N = 2e7L
+DT = data.table(x = sample(letters, N, TRUE),
+ y = sample(1000L, N, TRUE),
+ val = runif(N))
+print(object.size(DT), units = "MiB")
+```
+
+`DT` ocupa unos 380 MiB. No es un tamaño enorme, pero basta con esto para ilustrar el punto.
+
+De lo que hemos visto en la sección Introducción a data.table, podemos filtrar aquellas filas donde las columnas `x = "g"` e `y = 877` de la siguiente manera:
+
+```{r}
+key(DT)
+## (1) Usual way of subsetting - vector scan approach
+t1 <- system.time(ans1 <- DT[x == "g" & y == 877L])
+t1
+head(ans1)
+dim(ans1)
+```
+
+Ahora vamos a intentar crear filtros usando claves.
+
+```{r}
+setkeyv(DT, c("x", "y"))
+key(DT)
+## (2) Subsetting using keys
+t2 <- system.time(ans2 <- DT[.("g", 877L)])
+t2
+head(ans2)
+dim(ans2)
+
+identical(ans1$val, ans2$val)
+```
+
+* La mejora en velocidad es **~`{r} round(t1[3]/max(t2[3], .001))`x**!
+
+### b) ¿Por qué al introducir una clave en una *data.table* se obtienen filtros increíblemente rápidos?
+
+Para entender esto, veamos primero qué hace el *enfoque de escaneo vectorial* (método 1).
+
+#### Enfoque de escaneo vectorial
+
+* Se busca en la columna `x` el valor *"g"* fila por fila, en los 20 millones de filas. Esto da como resultado un *vector lógico* de tamaño 20 millones, con valores `TRUE, FALSE o NA` correspondientes al valor de `x`.
+
+* De manera similar, se busca `877` en la columna `y` en las 20 millones de filas una por una, y se almacena en otro vector lógico.
+
+* Las operaciones `&` elemento por elemento se realizan en los vectores lógicos intermedios y se devuelven todas las filas donde la expresión se evalúa como `VERDADERO`.
+
+Esto es lo que llamamos un *enfoque de escaneo vectorial*. Es bastante ineficiente, especialmente en tablas grandes y cuando se necesita crear filtros repetidamente, ya que tiene que escanear todas las filas cada vez.
+
+Ahora veamos el método de búsqueda binaria (método 2). Recordemos de [Propiedades de la clave](#key-properties): *establecer claves reordena la tabla data.table por columnas clave*. Como los datos están ordenados, ¡no tenemos que *explorar toda la longitud de la columna*! En su lugar, podemos usar la *búsqueda binaria* para buscar un valor en `O(log n)`, en lugar de `O(n)`, como en el caso del *enfoque de escaneo vectorial*, donde `n` es el número de filas en la tabla data.table*.
+
+#### Enfoque de búsqueda binaria
+
+Aquí hay una ilustración muy simple. Consideremos los números (ordenados) que se muestran a continuación:
+
+```
+1, 5, 10, 19, 22, 23, 30
+```
+
+Supongamos que queremos encontrar la posición coincidente del valor *1*, usando la búsqueda binaria, así es como procederíamos, porque sabemos que los datos están *ordenados*.
+
+* Comienza con el valor del medio = 19. ¿Es 1 == 19? N.° 1 < 19.
+
+* Dado que el valor que buscamos es menor que 19, debería estar en algún lugar antes de 19. Por lo tanto, podemos descartar el resto de la mitad que sea >= 19.
+
+* Nuestro conjunto se reduce a *1, 5, 10*. Toma el valor medio una vez más = 5. ¿Es 1 == 5? No. 1 < 5.
+
+* Nuestro conjunto se reduce a *1*. ¿Es 1 == 1? Sí. El índice correspondiente también es 1. Y esa es la única coincidencia.
+
+Por otro lado, un enfoque de escaneo vectorial tendría que escanear todos los valores (aquí, 7).
+
+Se puede observar que con cada búsqueda reducimos el número de búsquedas a la mitad. Por eso, los filtros basados en *búsquedas binarias* son **increíblemente rápidos**. Dado que las filas de cada columna de *data.tables* tienen ubicaciones contiguas en memoria, las operaciones se realizan con gran eficiencia de caché (lo que también contribuye a la *velocidad*).
+
+Además, dado que obtenemos los índices de fila coincidentes directamente sin tener que crear esos enormes vectores lógicos (iguales al número de filas en una *data.table*), también es bastante **eficiente en términos de memoria**.
+
+## Resumen
+
+En esta viñeta, hemos aprendido otro método para crear filtros de filas en `i` mediante la introducción de claves en una *data.table*. La introducción de claves nos permite realizar filtros increíblemente rápidos mediante la búsqueda binaria. En particular, hemos visto cómo
+
+* establecer clave y filtrar usando la clave en una *data.table*.
+
+* filtrar utilizando claves que obtienen *índices de fila* en `i`, pero mucho más rápido.
+
+* Combina filtros basados en claves con `j` y `by`. Ten en cuenta que las operaciones `j` y `by` son exactamente las mismas que antes.
+
+Los filtros basados en claves son **increíblemente rápidos** y resultan especialmente útiles cuando la tarea implica la aplicación repetida de filtros. Sin embargo, no siempre es recomendable establecer la clave y reordenar físicamente la *data.table*. En la siguiente viñeta (`vignette("datatable-secondary-indices-and-auto-indexing", package="data.table")`)](datatable-secondary-indices-and-auto-indexing.html), abordaremos este problema mediante una nueva función: los índices secundarios.
+
+```{r, echo=FALSE}
+setDTthreads(.old.th)
+```
diff --git a/vignettes/es/datatable-programming.Rmd b/vignettes/es/datatable-programming.Rmd
new file mode 100644
index 000000000..d3034d2af
--- /dev/null
+++ b/vignettes/es/datatable-programming.Rmd
@@ -0,0 +1,485 @@
+---
+title: "Programación en data.table"
+date: "`{r} Sys.Date()`"
+output:
+ litedown::html_format
+vignette: >
+ %\VignetteIndexEntry{Programming on data.table}
+ %\VignetteEngine{litedown::vignette}
+ \usepackage[utf8]{inputenc}
+---
+
+```{r, echo=FALSE, file='../_translation_links.R'}
+```
+
+`{r} .write.translation.links("Las traducciones de este documento están disponibles en: %s")`
+
+```{r init, include = FALSE}
+require(data.table)
+litedown::reactor(comment = "# ")
+```
+
+## Introducción
+
+Desde sus primeras versiones, `data.table` habilitó el uso de las funciones `subset` y `with` (o `within`) mediante la definición del método `[.data.table`. `subset` y `with` son funciones básicas de R útiles para reducir la repetición en el código, mejorar la legibilidad y reducir la cantidad de caracteres que el usuario debe escribir. Esta funcionalidad es posible en R gracias a una característica única llamada *evaluación diferida*. Esta característica permite que una función capture sus argumentos antes de que se evalúen y los evalúe en un ámbito diferente al que se invocó. Recapitulemos el uso de la función `subset`.
+
+```{r df_print, echo=FALSE}
+registerS3method("print", "data.frame", function(x, ...) {
+ base::print.data.frame(head(x, 2L), ...)
+ cat("...\n")
+ invisible(x)
+})
+.opts = options(
+ datatable.print.topn=2L,
+ datatable.print.nrows=20L
+)
+```
+
+```{r subset}
+subset(iris, Species == "setosa")
+```
+
+Aquí, `subset` toma el segundo argumento y lo evalúa dentro del alcance del `data.frame` dado como primer argumento. Esto elimina la necesidad de repetir variables, lo que lo hace menos propenso a errores y hace que el código sea más legible.
+
+## Descripción del problema
+
+El problema con este tipo de interfaz es que no es fácil parametrizar el código que la utiliza. Esto se debe a que las expresiones pasadas a esas funciones se sustituyen antes de ser evaluadas.
+
+### Ejemplo
+
+```{r subset_error, error=TRUE, purl=FALSE}
+my_subset = function(data, col, val) {
+ subset(data, col == val)
+}
+my_subset(iris, Species, "setosa")
+```
+
+### Aproximaciones al problema
+
+Hay varias formas de solucionar este problema.
+
+#### Evitar la *evaluación perezosa*
+
+La solución más sencilla es evitar la *evaluación perezosa* en primer lugar y recurrir a enfoques menos intuitivos y más propensos a errores, como `df[["variable"]]`, etc.
+
+```{r subset_nolazy}
+my_subset = function(data, col, val) {
+ data[data[[col]] == val & !is.na(data[[col]]), ]
+}
+my_subset(iris, col = "Species", val = "setosa")
+```
+
+Aquí, calculamos un vector lógico de longitud `nrow(iris)`, que se introduce en el argumento `i` de `[.data.frame` para realizar subconjuntos basados en vectores lógicos. Para alinearlo con `subset()`, que también omite NA, necesitamos incluir un uso adicional de `data[[col]]` para capturarlo. Funciona bien para este ejemplo sencillo, pero carece de flexibilidad, introduce repetición de variables y requiere que el usuario modifique la interfaz de la función para pasar el nombre de la columna como un carácter en lugar de un símbolo sin comillas. Cuanto más compleja sea la expresión que necesitamos parametrizar, menos práctico resulta este enfoque.
+
+#### Uso de `parse` / `eval`
+
+Este método suele ser el preferido por quienes se inician en R, ya que es, quizás, el más sencillo conceptualmente. Requiere generar la expresión requerida mediante concatenación de cadenas, analizarla y evaluarla.
+
+```{r subset_parse}
+my_subset = function(data, col, val) {
+ data = deparse(substitute(data))
+ col = deparse(substitute(col))
+ val = paste0("'", val, "'")
+ text = paste0("subset(", data, ", ", col, " == ", val, ")")
+ eval(parse(text = text)[[1L]])
+}
+my_subset(iris, Species, "setosa")
+```
+
+Debemos usar `deparse(substitute(...))` para capturar los nombres reales de los objetos pasados a la función, de modo que podamos construir la llamada a la función `subset` usando esos nombres originales. Si bien esto ofrece una flexibilidad ilimitada con una complejidad relativamente baja, **se debe evitar el uso de `eval(parse(...))`**. Las principales razones son:
+
+- falta de validación de sintaxis
+- [vulnerabilidad a la inyección de código](https://github.com/Rdatatable/data.table/issues/2655#issuecomment-376781159)
+- la existencia de mejores alternativas
+
+Martin Machler, desarrollador principal del proyecto R, [dijo una vez](https://stackoverflow.com/a/40164111/2490497):
+
+> Lo siento, pero no entiendo por qué tanta gente piensa siquiera que una cadena es algo que se puede evaluar. Debes cambiar de mentalidad, de verdad. Olvídense de todas las conexiones entre cadenas por un lado y expresiones, llamadas y evaluación por el otro. La (posiblemente) única conexión es mediante `parse(text = ....)`, y todo buen programador de R debería saber que esto rara vez es un método eficiente o seguro para construir expresiones (o llamadas). Mejor aprenda más sobre `substitute()`, `quote()` y, posiblemente, el poder de usar `do.call(substitute, ......)`.
+
+#### Computación sobre el lenguaje
+
+Las funciones mencionadas anteriormente, junto con algunas otras (incluidas `as.call`, `as.name`/`as.symbol`, `bquote` y `eval`), se pueden categorizar como funciones para *calcular en el lenguaje*, ya que operan en objetos del *lenguaje* (por ejemplo, `call`, `name`/`symbol`).
+
+```{r subset_substitute}
+my_subset = function(data, col, val) {
+ eval(substitute(subset(data, col == val)))
+}
+my_subset(iris, Species, "setosa")
+```
+
+Aquí, usamos la función base R `substitute` para transformar la llamada `subset(data, col == val)` en `subset(iris, Species == "setosa")` sustituyendo `data`, `col` y `val` por sus nombres (o valores) originales de su entorno padre. Las ventajas de este enfoque con respecto a los anteriores son evidentes. Cabe destacar que, dado que operamos a nivel de objetos del lenguaje y no es necesario manipular cadenas, lo denominamos *computación en el lenguaje*. El [manual del lenguaje R](https://cran.r-project.org/doc/manuals/r-release/R-lang.html) incluye un capítulo dedicado a *computación en el lenguaje*. Aunque no es necesario para *programar en data.table*, recomendamos leer este capítulo para comprender mejor esta potente y única característica del lenguaje R.
+
+#### Utilizar paquetes de terceros
+
+Hay paquetes de terceros que pueden lograr lo que la computación R basa en las rutinas del lenguaje (`pryr`, `lazyeval` y `rlang`, por nombrar algunos).
+
+Si bien estos pueden ser útiles, aquí discutiremos un enfoque exclusivo de `data.table`.
+
+## Programación en data.table
+
+Ahora que hemos establecido la forma correcta de parametrizar el código que utiliza *evaluación perezosa*, podemos pasar al tema principal de esta viñeta, *programación en data.table*.
+
+A partir de la versión 1.15.0, data.table proporciona un mecanismo robusto para parametrizar expresiones pasadas a los argumentos `i`, `j` y `by` (o `keyby`) de `[.data.table`. Se basa en la función `substitute` de R e imita su interfaz. Aquí presentamos `substitute2` como una versión más robusta y fácil de usar de `substitute` de R. Para obtener una lista completa de las diferencias entre `base::substitute` y `data.table::substitute2`, consulte el [manual de `substitute2`](https://rdatatable.gitlab.io/data.table/library/data.table/html/substitute2.html).
+
+### Sustituir variables y nombres
+
+Supongamos que queremos una función general que aplique una función a la suma de dos argumentos a los que se les ha aplicado otra función. Como ejemplo concreto, a continuación tenemos una función para calcular la longitud de la hipotenusa en un triángulo rectángulo, conociendo la longitud de sus catetos.
+
+${\displaystyle c = \sqrt{a^2 + b^2}}$
+
+```{r hypotenuse}
+square = function(x) x^2
+quote(
+ sqrt(square(a) + square(b))
+)
+```
+
+El objetivo es hacer que cada nombre en la llamada anterior pueda pasarse como parámetro.
+
+```{r hypotenuse_substitute2}
+substitute2(
+ outer(inner(var1) + inner(var2)),
+ env = list(
+ outer = "sqrt",
+ inner = "square",
+ var1 = "a",
+ var2 = "b"
+ )
+)
+```
+
+Podemos ver en la salida que se han reemplazado tanto los nombres de las funciones como los de las variables pasadas a ellas. Usamos `substitute2` por comodidad. En este caso simple, también se podría haber usado `substitute` de R base, aunque habría requerido el uso de `lapply(env, as.name)`.
+
+Ahora, para usar la sustitución dentro de `[.data.table`, no necesitamos llamar a la función `substitute2`. Como ahora se usa internamente, solo tenemos que proporcionar el argumento `env`, de la misma manera que lo hicimos con la función `substitute2` en el ejemplo anterior. La sustitución se puede aplicar a los argumentos `i`, `j` y `by` (o `keyby`) del método `[.data.table`. Tenga en cuenta que establecer el argumento `verbose` como `TRUE` permite imprimir expresiones después de aplicar la sustitución. Esto es muy útil para la depuración.
+
+Usemos el conjunto de datos "iris" como demostración. A modo de ejemplo, imaginemos que queremos calcular la "Hipotenusa del Sépalo", considerando el ancho y la longitud del sépalo como si fueran los catetos de un triángulo rectángulo.
+
+```{r hypotenuse_datatable}
+DT = as.data.table(iris)
+
+str(
+ DT[, outer(inner(var1) + inner(var2)),
+ env = list(
+ outer = "sqrt",
+ inner = "square",
+ var1 = "Sepal.Length",
+ var2 = "Sepal.Width"
+ )]
+)
+
+# return as a data.table
+DT[, .(Species, var1, var2, out = outer(inner(var1) + inner(var2))),
+ env = list(
+ outer = "sqrt",
+ inner = "square",
+ var1 = "Sepal.Length",
+ var2 = "Sepal.Width",
+ out = "Sepal.Hypotenuse"
+ )]
+```
+
+En la última llamada, añadimos otro parámetro, `out = "Sepal.Hypotenuse"`, que indica el nombre de la columna de salida. A diferencia del `substitute` de R base, `substitute2` también gestionará la sustitución de los nombres de los argumentos de la llamada.
+
+La sustitución también funciona en `i` y `by` (o `keyby`).
+
+```{r hypotenuse_datatable_i_j_by}
+DT[filter_col %in% filter_val,
+ .(var1, var2, out = outer(inner(var1) + inner(var2))),
+ by = by_col,
+ env = list(
+ outer = "sqrt",
+ inner = "square",
+ var1 = "Sepal.Length",
+ var2 = "Sepal.Width",
+ out = "Sepal.Hypotenuse",
+ filter_col = "Species",
+ filter_val = I(c("versicolor", "virginica")),
+ by_col = "Species"
+ )]
+```
+
+### Sustituir funciones
+
+Una pequeña aclaración puede ser útil sobre cómo sustituir el nombre de una función en una expresión. Tenga en cuenta que proporcionar `outer="sqrt"` (cadena) y `outer=sqrt` (símbolo) es muy diferente:
+
+```{r substitute_fun1, result='hide'}
+DT[, outer(Sepal.Length), env = list(outer="sqrt"), verbose=TRUE]
+#Argument 'j' after substitute: sqrt(Sepal.Length)
+## DT[, sqrt(Sepal.Length)]
+
+DT[, outer(Sepal.Length), env = list(outer=sqrt), verbose=TRUE]
+#Argument 'j' after substitute: .Primitive("sqrt")(Sepal.Length)
+## DT[, .Primitive("sqrt")(Sepal.Length)]
+```
+
+Y aunque `.Primitive("sqrt")(Sepal.Length)` todavía funciona, casi nunca es la forma deseada.
+
+Más importante aún, si se pretende utilizar la forma del símbolo, entonces se puede y se debe utilizar directamente en la expresión, ya que no hay necesidad de sustitución.
+
+```{r substitute_fun2, result='hide'}
+DT[, sqrt(Sepal.Length)]
+```
+
+Si el nombre de la función que se va a sustituir necesita estar calificado por el espacio de nombres, entonces el espacio de nombres y el nombre de la función se pueden sustituir por cualquier otro símbolo en la expresión:
+
+```r
+DT[, ns::fun(Sepal.Length), env = list(ns="base", fun="sqrt"), verbose=TRUE]
+#Argument 'j' after substitute: base::sqrt(Sepal.Length)
+## DT[, base::sqrt(Sepal.Length)]
+```
+
+### Sustituir variables y valores de caracteres
+
+En el ejemplo anterior, hemos visto una característica útil de `substitute2`: la conversión automática de cadenas a nombres/símbolos. Surge una pregunta obvia: ¿qué ocurre si realmente queremos sustituir un parámetro con un valor de *carácter* para obtener el comportamiento `substitute` de R base? Proporcionamos un mecanismo para evitar la conversión automática envolviendo los elementos en la llamada `I()` de R base. La función `I` marca un objeto como *AsIs*, lo que impide la conversión automática de sus argumentos de carácter a símbolo. (Consulte la documentación de `?AsIs` para más detalles). Si se desea el comportamiento de R base para todo el argumento `env`, lo mejor es envolverlo en `I()`. Alternativamente, cada elemento de la lista puede envolverse en `I()` individualmente. Analicemos ambos casos a continuación.
+
+```{r rank}
+substitute( # base R behaviour
+ rank(input, ties.method = ties),
+ env = list(input = as.name("Sepal.Width"), ties = "first")
+)
+
+substitute2( # mimicking base R's "substitute" using "I"
+ rank(input, ties.method = ties),
+ env = I(list(input = as.name("Sepal.Width"), ties = "first"))
+)
+
+substitute2( # only particular elements of env are used "AsIs"
+ rank(input, ties.method = ties),
+ env = list(input = "Sepal.Width", ties = I("first"))
+)
+```
+
+Tenga en cuenta que la conversión funciona de forma recursiva en cada elemento de la lista, incluido el mecanismo de escape, por supuesto.
+
+```{r substitute2_recursive}
+substitute2( # all are symbols
+ f(v1, v2),
+ list(v1 = "a", v2 = list("b", list("c", "d")))
+)
+substitute2( # 'a' and 'd' should stay as character
+ f(v1, v2),
+ list(v1 = I("a"), v2 = list("b", list("c", I("d"))))
+)
+```
+
+### Sustitución de listas de longitud arbitraria
+
+El ejemplo anterior ilustra una forma sencilla y eficaz de dinamizar el código. Sin embargo, existen muchos otros casos mucho más complejos con los que un desarrollador podría tener que lidiar. Un problema común es el manejo de una lista de argumentos de longitud arbitraria.
+
+Un caso de uso obvio podría ser imitar la funcionalidad de `.SD` inyectando una llamada `list` en el argumento `j`.
+
+```{r splice_sd}
+cols = c("Sepal.Length", "Sepal.Width")
+DT[, .SD, .SDcols = cols]
+```
+
+Teniendo el parámetro `cols`, nos gustaría unirlo en una llamada `list`, haciendo que el argumento `j` se vea como en el código a continuación.
+
+```{r splice_tobe}
+DT[, list(Sepal.Length, Sepal.Width)]
+```
+
+*Splicing* es una operación que consiste en insertar una lista de objetos en una expresión como una secuencia de argumentos para llamar. En R base, empalmar `cols` en una `list` se puede lograr usando `as.call(c(quote(list), lapply(cols, as.name)))`. Además, a partir de R 4.0.0, existe una nueva interfaz para esta operación en la función `bquote`.
+
+En data.table, lo simplificamos al incluir automáticamente una lista de objetos en una llamada de lista con esos objetos. Esto significa que cualquier objeto `list` dentro del argumento `env` se convertirá en una `call` de lista, simplificando así la API para ese caso de uso, como se muestra a continuación.
+
+```{r splice_datatable}
+# this works
+DT[, j,
+ env = list(j = as.list(cols)),
+ verbose = TRUE]
+
+# this will not work
+#DT[, list(cols),
+# env = list(cols = cols)]
+```
+
+Es importante proporcionar una llamada a `as.list`, en lugar de simplemente una lista, dentro del argumento de lista `env`, como se muestra en el ejemplo anterior.
+
+Exploremos el *alistamiento* con más detalle.
+
+```{r splice_enlist}
+DT[, j, # data.table automatically enlists nested lists into list calls
+ env = list(j = as.list(cols)),
+ verbose = TRUE]
+
+DT[, j, # turning the above 'j' list into a list call
+ env = list(j = quote(list(Sepal.Length, Sepal.Width))),
+ verbose = TRUE]
+
+DT[, j, # the same as above but accepts character vector
+ env = list(j = as.call(c(quote(list), lapply(cols, as.name)))),
+ verbose = TRUE]
+```
+
+Ahora, intentemos pasar una lista de símbolos, en lugar de llamarlos. Usaremos `I()` para evitar la conversión automática de *enlist*, pero como esto también desactivará la conversión de caracteres a símbolos, también debemos usar `as.name`.
+
+```{r splice_not, error=TRUE, purl=FALSE}
+DT[, j, # list of symbols
+ env = I(list(j = lapply(cols, as.name))),
+ verbose = TRUE]
+
+DT[, j, # again the proper way, enlist list to list call automatically
+ env = list(j = as.list(cols)),
+ verbose = TRUE]
+```
+
+Téngase en cuenta que ambas expresiones, aunque visualmente parezcan iguales, no son idénticas.
+
+```{r splice_substitute2_not}
+str(substitute2(j, env = I(list(j = lapply(cols, as.name)))))
+
+str(substitute2(j, env = list(j = as.list(cols))))
+```
+
+Para obtener una explicación más detallada sobre este asunto, consulte los ejemplos en la [documentación de `substitute2`](https://rdatatable.gitlab.io/data.table/library/data.table/html/substitute2.html).
+
+### Sustitución de una consulta compleja
+
+Tomemos, como ejemplo de una función más compleja, el cálculo de la raíz cuadrada media.
+
+${\displaystyle x_{\text{RMS}}={\sqrt{{\frac{1}{n}}\left(x_{1}^{2}+x_{2}^{2}+\cdots +x_{n}^{2}\right)}}}$
+
+Acepta un número arbitrario de variables como entrada, pero ahora no podemos simplemente "unir" una lista de argumentos en una llamada de lista, ya que cada uno de ellos debe estar encapsulado en una llamada "cuadrada". En este caso, debemos "unir" manualmente en lugar de depender del "enlistado" automático de data.table.
+
+Primero, debemos construir llamadas a la función `square` para cada variable (ver `inner_calls`). Luego, debemos reducir la lista de llamadas a una sola, con una secuencia anidada de llamadas `+` (ver `add_calls`). Finalmente, debemos sustituir la llamada construida en la expresión circundante (ver `rms`).
+
+```{r complex}
+outer = "sqrt"
+inner = "square"
+vars = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width")
+
+syms = lapply(vars, as.name)
+to_inner_call = function(var, fun) call(fun, var)
+inner_calls = lapply(syms, to_inner_call, inner)
+print(inner_calls)
+
+to_add_call = function(x, y) call("+", x, y)
+add_calls = Reduce(to_add_call, inner_calls)
+print(add_calls)
+
+rms = substitute2(
+ expr = outer((add_calls) / len),
+ env = list(
+ outer = outer,
+ add_calls = add_calls,
+ len = length(vars)
+ )
+)
+print(rms)
+
+str(
+ DT[, j, env = list(j = rms)]
+)
+
+# same, but skipping last substitute2 call and using add_calls directly
+str(
+ DT[, outer((add_calls) / len),
+ env = list(
+ outer = outer,
+ add_calls = add_calls,
+ len = length(vars)
+ )]
+)
+
+# return as data.table
+j = substitute2(j, list(j = as.list(setNames(nm = c(vars, "Species", "rms")))))
+j[["rms"]] = rms
+print(j)
+DT[, j, env = list(j = j)]
+
+# alternatively
+j = as.call(c(
+ quote(list),
+ lapply(setNames(nm = vars), as.name),
+ list(Species = as.name("Species")),
+ list(rms = rms)
+))
+print(j)
+DT[, j, env = list(j = j)]
+```
+
+### Errores comunes
+
+Es importante comprender la diferencia entre pasar un objeto y un nombre que apunta a un objeto. Vea la salida detallada de los siguientes ejemplos.
+
+```{r obj_vs_objname}
+DT[, fun(Petal.Width), env = list(fun = mean), verbose=TRUE]
+DT[, fun(Petal.Width), env = list(fun = "mean"), verbose=TRUE]
+```
+
+Los usuarios suelen preferir sustituir el nombre de la función en lugar de insertar el cuerpo de la misma. Por lo tanto, suele preferirse el segundo enfoque (pasar una cadena de caracteres).
+
+En caso de dudas sobre el funcionamiento de la interfaz `env`, establezca `verbose = TRUE` para inspeccionar cómo se resuelven las expresiones internamente.
+
+### Utilice el argumento `env` desde dentro de otra función
+
+Se decidió por diseño que el argumento `env` siga las reglas de *Evaluación Estándar* (SE), es decir, los valores pasados a `env` se evalúan en su ámbito original tal cual. Para más información sobre el tema, consulte el [Manual del lenguaje R: Computación en el lenguaje](https://cran.r-project.org/doc/manuals/r-release/R-lang.html#Computing-on-the-language)). Por lo tanto, **usar el argumento `env` desde una función no requiere ningún manejo especial**. Esto también significa que el alias `.()` para una `list()`, *como* `env = .(.col="Petal.Length")`, no funcionará; utilice `env = list(.col="Petal.Length")` en su lugar.
+
+```{r env_se}
+fun = function(x, col.mean) {
+ stopifnot(is.character(col.mean), is.data.table(x))
+ x[, .(col_avg = mean(.col)), env = list(.col = col.mean)]
+}
+fun(DT, col.mean="Petal.Length")
+```
+
+Si la función externa en sí sigue las reglas NSE (evaluación no estándar), entonces tiene que resolver los objetos del lenguaje de la misma manera que cuando pasa sus argumentos a cualquier otra función SE.
+
+```{r env_nse}
+fun = function(x, col.mean) {
+ col.mean = substitute(col.mean)
+ stopifnot(is.name(col.mean), is.data.table(x))
+ x[, .(col_avg = mean(.col)), env = list(.col = col.mean)]
+}
+fun(DT, col.mean=Petal.Length)
+```
+
+## Interfaces retiradas
+
+En `[.data.table`, también es posible usar otros mecanismos para la sustitución de variables o para pasar expresiones entre comillas. Estos incluyen `get` y `mget` para la inyección en línea de variables proporcionando sus nombres como cadenas, y `eval`, que indica a `[.data.table` que la expresión pasada a un argumento está entre comillas y que debe gestionarse de forma diferente. Estas interfaces deben considerarse retiradas y recomendamos usar el nuevo argumento `env` en su lugar.
+
+### `get`
+
+```{r old_get}
+v1 = "Petal.Width"
+v2 = "Sepal.Width"
+
+DT[, .(total = sum(get(v1), get(v2)))]
+
+DT[, .(total = sum(v1, v2)),
+ env = list(v1 = v1, v2 = v2)]
+```
+
+### `mget`
+
+```{r old_mget}
+v = c("Petal.Width", "Sepal.Width")
+
+DT[, lapply(mget(v), mean)]
+
+DT[, lapply(v, mean),
+ env = list(v = as.list(v))]
+
+DT[, lapply(v, mean),
+ env = list(v = as.list(setNames(nm = v)))]
+```
+
+### `eval`
+
+En lugar de utilizar la función `eval`, podemos proporcionar una expresión entre comillas en el elemento del argumento `env`, por lo que no se necesita una llamada `eval` adicional.
+
+```{r old_eval}
+cl = quote(
+ .(Petal.Width = mean(Petal.Width), Sepal.Width = mean(Sepal.Width))
+)
+
+DT[, eval(cl)]
+
+DT[, cl, env = list(cl = cl)]
+```
+
+```{r cleanup, echo=FALSE}
+options(.opts)
+registerS3method("print", "data.frame", base::print.data.frame)
+```
diff --git a/vignettes/es/datatable-reference-semantics.Rmd b/vignettes/es/datatable-reference-semantics.Rmd
new file mode 100644
index 000000000..b6a4e32e4
--- /dev/null
+++ b/vignettes/es/datatable-reference-semantics.Rmd
@@ -0,0 +1,413 @@
+---
+title: "Semántica de referencia"
+date: "`{r} Sys.Date()`"
+output:
+ litedown::html_format
+vignette: >
+ %\VignetteIndexEntry{Reference semantics}
+ %\VignetteEngine{litedown::vignette}
+ \usepackage[utf8]{inputenc}
+---
+
+```{r, echo=FALSE, file='../_translation_links.R'}
+```
+
+`{r} .write.translation.links("Las traducciones de este documento están disponibles en: %s")`
+
+```{r, echo = FALSE, message = FALSE}
+library(data.table)
+litedown::reactor(comment = "# ")
+.old.th = setDTthreads(1)
+```
+
+Esta viñeta describe la semántica por referencia de *data.table*, que permite *añadir/actualizar/eliminar* columnas de una *data.table* por referencia*, así como combinarlas con `i` y `by`. Está dirigida a quienes ya están familiarizados con la sintaxis de *data.table*, su forma general, cómo crear subconjuntos de filas en `i`, seleccionar y calcular columnas, y realizar agregaciones por grupo. Si no está familiarizado con estos conceptos, lea primero la viñeta [`vignette("datatable-intro", package="data.table")`](datatable-intro.html).
+
+***
+
+## Datos {#data}
+
+Utilizaremos los mismos datos de `flights` que en la viñeta [`vignette("datatable-intro", package="data.table")`](datatable-intro.html).
+
+```{r, echo = FALSE}
+options(width = 100L)
+```
+
+```{r}
+flights <- fread("../flights14.csv")
+flights
+dim(flights)
+```
+
+## Introducción
+
+En esta viñeta, vamos a:
+
+1. Primero analicemos brevemente la semántica por referencia y observemos las dos formas diferentes en las que se puede utilizar el operador `:=`
+
+2. Luego veamos cómo podemos *agregar/actualizar/eliminar* columnas *por referencia* en `j` usando el operador `:=` y cómo combinarlo con `i` y `by`.
+
+3. y finalmente veremos el uso de `:=` por sus *efectos secundarios* y cómo podemos evitar los efectos secundarios usando `copy()`.
+
+## 1. Semántica por referencia
+
+Todas las operaciones que vimos en la viñeta anterior generaron un nuevo conjunto de datos. Veremos cómo *añadir* nuevas columnas, *actualizar* o *eliminar* columnas existentes en los datos originales.
+
+### a) Antecedentes
+
+Antes de analizar la *semántica por referencia*, considere el *data.frame* que se muestra a continuación:
+
+```{r}
+DF = data.frame(ID = c("b","b","b","a","a","c"), a = 1:6, b = 7:12, c = 13:18)
+DF
+```
+
+Cuando lo hicimos:
+
+```r
+DF$c <- 18:13 # (1) -- replace entire column
+# or
+DF$c[DF$ID == "b"] <- 15:13 # (2) -- subassign in column 'c'
+```
+
+Tanto (1) como (2) resultaban en una copia profunda de todo el data.frame en versiones de `R < 3.1`. [Se copiaba más de una vez](https://stackoverflow.com/q/23898969/559784). Para mejorar el rendimiento y evitar estas copias redundantes, *data.table* utilizó el operador `:=` [disponible pero no utilizado en R](https://stackoverflow.com/q/7033106/559784).
+
+Se implementaron importantes mejoras de rendimiento en `R v3.1`, lo que permite realizar una copia superficial para (1) y no una copia profunda. Sin embargo, para (2), la columna completa se copia en profundidad, incluso en `R v3.1+`. Esto significa que cuantas más columnas se subasignan en la misma consulta, más copias profundas realiza R.
+
+#### Copia *superficial* vs. copia *profunda*
+
+Una copia superficial es simplemente una copia del vector de punteros de columna (correspondientes a las columnas de un data.frame o una data.table). Los datos reales no se copian físicamente en la memoria.
+
+Una copia *profunda*, por otro lado, copia todos los datos a otra ubicación en la memoria.
+
+Al crear un subconjunto de una tabla de datos (data.table) mediante `i` (p. ej., `DT[1:10]`), se realiza una copia profunda. Sin embargo, si `i` no se proporciona o es igual a `TRUE`, se realiza una copia superficial.
+
+#
+
+Con el operador `:=` de *data.table*, no se realizan copias en (1) ni (2), independientemente de la versión de R que se utilice. Esto se debe a que el operador `:=` actualiza las columnas de *data.table* in situ (por referencia).
+
+### b) El operador `:=`
+
+Se puede utilizar en `j` de dos maneras:
+
+(a) La forma `LHS := RHS`
+
+```r
+DT[, c("colA", "colB", ...) := list(valA, valB, ...)]
+
+# when you have only one column to assign to you
+# can drop the quotes and list(), for convenience
+DT[, colA := valA]
+```
+
+(b) La forma funcional
+
+```r
+DT[, `:=`(colA = valA, # valA is assigned to colA
+ colB = valB, # valB is assigned to colB
+ ...
+)]
+```
+
+Tenga en cuenta que el código anterior explica cómo usar `:=`. No son ejemplos prácticos. Comenzaremos a usarlos en la tabla de datos `flights` a partir de la siguiente sección.
+
+#
+
+* En (a), `LHS` toma un vector de caracteres de nombres de columnas y `RHS` una *lista de valores*. `RHS` solo necesita ser una `lista`, independientemente de cómo se genere (p. ej., usando `lapply()`, `list()`, `mget()`, `mapply()`, etc.). Esta forma suele ser fácil de programar y es especialmente útil cuando no se conocen de antemano las columnas a las que se asignarán valores.
+
+* Por otro lado, (b) es útil si desea anotar algunos comentarios para más tarde.
+
+* El resultado se devuelve *de forma invisible*.
+
+* Dado que `:=` está disponible en `j`, podemos combinarlo con las operaciones `i` y `by` tal como las operaciones de agregación que vimos en la viñeta anterior.
+
+#
+
+En las dos formas de `:=` mostradas arriba, observe que no asignamos el resultado a una variable, ya que no es necesario. La entrada *data.table* se modifica por referencia. Veamos algunos ejemplos para comprender a qué nos referimos.
+
+Para el resto de la viñeta, trabajaremos con la tabla de datos *flights*.
+
+## 2. Agregar/actualizar/eliminar columnas *por referencia*
+
+### a) Agregar columnas por referencia {#ref-j}
+
+#### -- ¿Cómo podemos agregar las columnas *velocidad* y *retraso total* de cada vuelo a la tabla de datos *flights*?
+
+```{r}
+flights[, `:=`(speed = distance / (air_time/60), # speed in mph (mi/h)
+ delay = arr_delay + dep_delay)] # delay in minutes
+head(flights)
+
+## alternatively, using the 'LHS := RHS' form
+# flights[, c("speed", "delay") := list(distance/(air_time/60), arr_delay + dep_delay)]
+```
+
+#### Tenga en cuenta que
+
+* No tuvimos que volver a asignar el resultado a `flights`.
+
+* La tabla de datos `flights` ahora contiene las dos columnas recién añadidas. Esto es lo que queremos decir con `añadidas por referencia`.
+
+* Usamos la forma funcional para poder agregar comentarios al margen y explicar el cálculo. También puedes ver la forma `LHS := RHS` (comentada).
+
+### b) Actualizar algunas filas de columnas por referencia - *sub-asignar* por referencia {#ref-ij}
+
+Echemos un vistazo a todas las `hours` disponibles en la *data.table* `flights`:
+
+```{r}
+# get all 'hours' in flights
+flights[, sort(unique(hour))]
+```
+
+Observamos que hay un total de `25` valores únicos en los datos. Parece que hay tanto *0* como *24* horas. Reemplacemos *24* por *0*.
+
+#### -- Reemplace aquellas filas donde `hora == 24` con el valor `0`
+
+```{r}
+# subassign by reference
+flights[hour == 24L, hour := 0L]
+```
+
+* Podemos usar `i` junto con `:=` en `j` de la misma manera que ya hemos visto en la viñeta [`vignette("datatable-intro", package="data.table")`](datatable-intro.html).
+
+* La columna `hora` se reemplaza con `0` solo en aquellos *índices de fila* donde la condición `hora == 24L` especificada en `i` se evalúa como `VERDADERO`.
+
+* `:=` devuelve el resultado de forma invisible. A veces, puede ser necesario ver el resultado después de la asignación. Podemos lograrlo añadiendo un `[]` vacío al final de la consulta, como se muestra a continuación:
+
+ ```{r}
+ flights[hour == 24L, hour := 0L][]
+ ```
+
+#
+
+Veamos todas las `hours` para verificar.
+
+```{r}
+# check again for '24'
+flights[, sort(unique(hour))]
+```
+
+#### Ejercicio: {#update-by-reference-question}
+
+¿Cuál es la diferencia entre `flights[hour == 24L, hour := 0L]` y `flights[hour == 24L][, hour := 0L]`? Consejo: Este último requiere una asignación (`<-`) si desea usar el resultado posteriormente.
+
+Si no puede resolverlo, eche un vistazo a la sección "Nota" de "?":="`.
+
+### c) Eliminar columna por referencia
+
+#### -- Eliminar la columna `delay`
+
+```{r}
+flights[, c("delay") := NULL]
+head(flights)
+
+## or using the functional form
+# flights[, `:=`(delay = NULL)]
+```
+
+#### {#eliminar-conveniencia}
+
+* Asignar `NULL` a una columna *elimina* esa columna. Y esto sucede *instantáneamente*.
+
+* También podemos pasar números de columnas en lugar de nombres en el `LHS`, aunque es una buena práctica de programación usar nombres de columnas.
+
+* Cuando solo hay una columna para eliminar, podemos omitir `c()` y las comillas dobles y usar solo el nombre de la columna *sin comillas*, para mayor comodidad. Es decir:
+
+ ```r
+ flights[, delay := NULL]
+ ```
+
+ is equivalent to the code above.
+
+### d) `:=` junto con la agrupación usando `by` {#ref-j-by}
+
+Ya vimos el uso de `i` junto con `:=` en la [Sección 2b](#ref-ij). Veamos ahora cómo podemos usar `:=` junto con `by`.
+
+#### -- ¿Cómo podemos agregar una nueva columna que contenga para cada par 'orig,dest' la velocidad máxima?
+
+```{r}
+flights[, max_speed := max(speed), by = .(origin, dest)]
+head(flights)
+```
+
+* Agregamos una nueva columna `max_speed` usando el operador `:=` por referencia.
+
+* Proporcionamos las columnas para agrupar de la misma manera que se muestra en la viñeta *Introducción a data.table*. Para cada grupo, se calcula `max(speed)`, que devuelve un único valor. Este valor se recicla para ajustarse a la longitud del grupo. Nuevamente, no se realizan copias. La tabla `flights` *data.table* se modifica *in situ*.
+
+* También podríamos haber proporcionado `by` con un *vector de caracteres* como vimos en la viñeta [`vignette("datatable-intro", package="data.table")`](datatable-intro.html), por ejemplo, `by = c("origin", "dest")`.
+
+#
+
+### e) Varias columnas y `:=`
+
+#### -- ¿Cómo podemos agregar dos columnas más calculando `max()` de `dep_delay` y `arr_delay` para cada mes, usando `.SD`?
+
+```{r}
+in_cols = c("dep_delay", "arr_delay")
+out_cols = c("max_dep_delay", "max_arr_delay")
+flights[, c(out_cols) := lapply(.SD, max), by = month, .SDcols = in_cols]
+head(flights)
+```
+
+* Usamos el formato `LHS := RHS`. Almacenamos los nombres de las columnas de entrada y las nuevas columnas que se agregarán en variables separadas y las proporcionamos a `.SDcols` y a `LHS` (para una mejor legibilidad).
+
+* Tenga en cuenta que, dado que permitimos la asignación por referencia sin comillas en los nombres de columna cuando solo hay una columna, como se explica en la [Sección 2c](#delete-convenience), no podemos usar `out_cols := lapply(.SD, max)`. Esto resultaría en agregar una nueva columna llamada `out_cols`. En su lugar, deberíamos usar `c(out_cols)` o simplemente `(out_cols)`. Encapsular el nombre de la variable con `(` es suficiente para diferenciar entre ambos casos.
+
+* La forma `LHS := RHS` permite operar en múltiples columnas. En el lado derecho, para calcular el `máximo` en las columnas especificadas en `.SDcols`, utilizamos la función base `lapply()` junto con `.SD`, tal como vimos anteriormente en la viñeta [`vignette("datatable-intro", package="data.table")`](datatable-intro.html). Esta función devuelve una lista de dos elementos, que contiene el valor máximo correspondiente a `dep_delay` y `arr_delay` para cada grupo.
+
+#
+
+Antes de pasar a la siguiente sección, limpiemos las columnas recién creadas `speed`, `max_speed`, `max_dep_delay` y `max_arr_delay`.
+
+```{r}
+# RHS gets automatically recycled to length of LHS
+flights[, c("speed", "max_speed", "max_dep_delay", "max_arr_delay") := NULL]
+head(flights)
+```
+
+#### -- ¿Cómo podemos actualizar varias columnas existentes usando `.SD`?
+
+```{r}
+flights[, names(.SD) := lapply(.SD, as.factor), .SDcols = is.character]
+```
+
+Limpiemos de nuevo y convirtamos nuestras columnas de factores recién creadas en columnas de caracteres. Esta vez, usaremos `.SDcols`, que acepta una función para decidir qué columnas incluir. En este caso, `is.factor()` devolverá las columnas que son factores. Para más información sobre el **S**subconjunto de **D**atos, también hay una viñeta de uso de SD (https://cran.r-project.org/package=data.table/vignettes/datatable-sd-usage.html).
+
+A veces, también es útil llevar un registro de las columnas que transformamos. De esta manera, incluso después de convertirlas, podremos llamar a las columnas específicas que actualizamos.
+
+```{r}
+factor_cols <- sapply(flights, is.factor)
+flights[, names(.SD) := lapply(.SD, as.character), .SDcols = factor_cols]
+str(flights[, ..factor_cols])
+```
+
+#### {.bs-callout .bs-callout-info}
+
+* También podríamos haber usado `(factor_cols)` en el `LHS` en lugar de `names(.SD)`.
+
+## 3. `:=` y `copy()`
+
+`:=` modifica el objeto de entrada por referencia. Además de las funciones que ya hemos mencionado, a veces podríamos querer usar la función de actualización por referencia por su efecto secundario. En otras ocasiones, puede que no sea conveniente modificar el objeto original, en cuyo caso podemos usar la función `copy()`, como veremos en breve.
+
+### a) `:=` por su efecto secundario
+
+Supongamos que queremos crear una función que devuelva la *velocidad máxima* de cada mes. Pero, al mismo tiempo, también queremos añadir la columna `velocidad` a `flights`. Podríamos escribir una función simple como la siguiente:
+
+```{r}
+foo <- function(DT) {
+ DT[, speed := distance / (air_time/60)]
+ DT[, .(max_speed = max(speed)), by = month]
+}
+ans = foo(flights)
+head(flights)
+head(ans)
+```
+
+* Tenga en cuenta que se ha añadido la nueva columna `speed` a la tabla de datos `flights`. Esto se debe a que `:=` realiza operaciones por referencia. Dado que `DT` (el argumento de la función) y `flights` hacen referencia al mismo objeto en memoria, modificar `DT` también afecta a `flights`.
+
+* Y `ans` contiene la velocidad máxima para cada mes.
+
+### b) La función `copy()`
+
+En la sección anterior, usamos `:=` por su efecto secundario. Sin embargo, esto no siempre es deseable. A veces, queremos pasar un objeto *data.table* a una función y usar el operador `:=`, pero no queremos actualizar el objeto original. Podemos lograrlo usando la función `copy()`.
+
+La función `copy()` copia *en profundidad* el objeto de entrada y, por lo tanto, cualquier operación de actualización por referencia posterior realizada en el objeto copiado no afectará al objeto original.
+
+#
+
+Hay dos lugares particulares donde la función `copy()` es esencial:
+
+1. A diferencia de lo visto en el punto anterior, es posible que no queramos que la tabla de datos de entrada de una función se modifique *por referencia*. Por ejemplo, consideremos la tarea de la sección anterior, excepto que no queremos modificar `flights` por referencia.
+
+ Let's first delete the `speed` column we generated in the previous section.
+
+ ```{r}
+ flights[, speed := NULL]
+ ```
+ Now, we could accomplish the task as follows:
+
+ ```{r}
+ foo <- function(DT) {
+ DT <- copy(DT) ## deep copy
+ DT[, speed := distance / (air_time/60)] ## doesn't affect 'flights'
+ DT[, .(max_speed = max(speed)), by = month]
+ }
+ ans <- foo(flights)
+ head(flights)
+ head(ans)
+ ```
+
+* El uso de la función `copy()` no actualizó la tabla de datos `flights` por referencia. No contiene la columna `speed`.
+
+* Y `ans` contiene la velocidad máxima correspondiente a cada mes.
+
+Sin embargo, podríamos mejorar esta funcionalidad aún más mediante una copia superficial en lugar de una copia profunda. De hecho, nos gustaría mucho [ofrecer esta funcionalidad para la versión `v1.9.8`](https://github.com/Rdatatable/data.table/issues/617). Volveremos a abordar este tema en la viñeta sobre el diseño de data.table.
+
+#
+
+2. Cuando almacenamos los nombres de las columnas en una variable, por ejemplo, `DT_n = names(DT)`, y luego *añadimos/actualizamos/eliminamos* columnas *por referencia*, también modificaríamos `DT_n`, a menos que hagamos `copy(names(DT))`.
+
+ ```{r}
+ DT = data.table(x = 1L, y = 2L)
+ DT_n = names(DT)
+ DT_n
+
+ ## add a new column by reference
+ DT[, z := 3L]
+
+ ## DT_n also gets updated
+ DT_n
+
+ ## use `copy()`
+ DT_n = copy(names(DT))
+ DT[, w := 4L]
+
+ ## DT_n doesn't get updated
+ DT_n
+ ```
+
+### c) Selección de columnas: `$` / `[[...]]` vs `[, col]`
+
+Cuando se extrae una sola columna como vector, existe una diferencia sutil pero importante entre los métodos R estándar ($ y [[...]]) y la expresión j de data.table. DT$col y DT[['col']] pueden devolver una referencia a la columna, mientras que DT[, col] siempre devuelve una copia.
+
+Un breve ejemplo:
+
+```{r}
+DT = data.table(a = 1:3)
+
+# three ways to get the column
+x_ref = DT$a # may be a reference
+y_cpy = DT[, a] # always a copy
+z_cpy = copy(DT$a) # forced copy
+
+# modify DT by reference
+DT[, a := a + 10L]
+
+# observe results
+x_ref # may show 11 12 13
+y_cpy # 1 2 3
+z_cpy # 1 2 3
+```
+
+Para seleccionar una sola columna como vector, recuerde:
+- `DT[, mycol]` es más seguro, ya que siempre devuelve una copia nueva e independiente.
+- `DT$mycol` es rápido, pero puede devolver una referencia. Use `copy(DT$mycol)` para garantizar la independencia.
+
+## Resumen
+
+#### El operador `:=`
+
+* Se utiliza para *agregar/actualizar/eliminar* columnas por referencia.
+
+* También vimos cómo usar `:=` junto con `i` y `by` de la misma manera que en la viñeta [`vignette("datatable-intro", package="data.table")`](datatable-intro.html). Podemos usar `keyby`, encadenar operaciones y pasar expresiones a `by` de la misma manera. La sintaxis es *consistente*.
+
+* Podemos usar `:=` por su efecto secundario o usar `copy()` para no modificar el objeto original mientras actualizamos por referencia.
+
+```{r, echo=FALSE}
+setDTthreads(.old.th)
+```
+
+#
+
+Hasta ahora hemos visto mucho sobre `j` y cómo combinarlo con `by` y poco de `i`. Volvamos a centrarnos en `i` en la [siguiente viñeta (`vignette("datatable-keys-fast-subset", package="data.table")`)](datatable-keys-fast-subset.html) para realizar subconjuntos ultrarrápidos mediante la *codificación de data.tables*.
+
+***
diff --git a/vignettes/es/datatable-reshape.Rmd b/vignettes/es/datatable-reshape.Rmd
new file mode 100644
index 000000000..8e520fe91
--- /dev/null
+++ b/vignettes/es/datatable-reshape.Rmd
@@ -0,0 +1,295 @@
+---
+title: "Remodelado eficiente con data.table"
+date: "`{r} Sys.Date()`"
+output:
+ litedown::html_format
+vignette: >
+ %\VignetteIndexEntry{Efficient reshaping using data.tables}
+ %\VignetteEngine{litedown::vignette}
+ \usepackage[utf8]{inputenc}
+---
+
+```{r, echo=FALSE, file='../_translation_links.R'}
+```
+
+`{r} .write.translation.links("Las traducciones de este documento están disponibles en: %s")`
+
+```{r, echo = FALSE, message = FALSE}
+library(data.table)
+litedown::reactor(comment = "# ")
+.old.th = setDTthreads(1)
+```
+
+Esta viñeta analiza el uso predeterminado de las funciones de remodelación `melt` (de ancho a largo) y `dcast` (de largo a ancho) para *data.tables*, así como las **nuevas funcionalidades extendidas** de fusión y conversión en *múltiples columnas* disponibles a partir de `v1.9.6`.
+
+***
+
+```{r, echo = FALSE}
+options(width = 100L)
+```
+
+## Datos
+
+Cargaremos los conjuntos de datos directamente dentro de las secciones.
+
+## Introducción
+
+Las funciones `melt` y `dcast` para `data.table` sirven para remodelar 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).
+
+En esta viñeta, vamos a:
+
+1. Primero, observar brevemente la conversión predeterminada de `melt` y `dcast` de `data.table` para convertirlos de formato *ancho* a *largo* y *viceversa*
+
+2. Analizar escenarios donde las funcionalidades actuales se vuelven engorrosas e ineficientes
+
+3. Finalmente, observar las nuevas mejoras en los métodos `melt` y `dcast` para que `data.table` gestione múltiples columnas simultáneamente.
+
+Las funcionalidades ampliadas están en línea con la filosofía de `data.table` de realizar operaciones de manera eficiente y sencilla.
+
+## 1. Funcionalidad predeterminada
+
+### a) fusión (`melt`ing) de `data.table` (de ancho a largo)
+
+Supongamos que tenemos una `data.table` (datos artificiales) como se muestra a continuación:
+
+```{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
+## dob stands for date of birth.
+
+str(DT)
+```
+
+#### - Convertir 'DT' a formato *largo* donde cada 'dob' es una observación separada.
+
+Podríamos lograr esto usando `melt()` especificando los argumentos `id.vars` y `measure.vars` de la siguiente manera:
+
+```{r}
+DT.m1 = melt(DT, id.vars = c("family_id", "age_mother"),
+ measure.vars = c("dob_child1", "dob_child2", "dob_child3"))
+DT.m1
+str(DT.m1)
+```
+
+* `measure.vars` especifica el conjunto de columnas que nos gustaría contraer (o combinar).
+
+* También podemos especificar *posiciones* de columna en lugar de *nombres*.
+
+* Por defecto, la columna `variable` es de tipo `factor`. Establezca el argumento `variable.factor` en `FALSE` si prefiere devolver un vector de tipo *`character`*.
+
+* De forma predeterminada, las columnas fundidas se denominan automáticamente `variable` y `value`.
+
+* `melt` conserva los atributos de la columna en el resultado.
+
+#### - Nombrar las columnas `variable` y `valor` como `hijo` y `fecha de nacimiento` respectivamente
+
+```{r}
+DT.m1 = melt(DT, measure.vars = c("dob_child1", "dob_child2", "dob_child3"),
+ variable.name = "child", value.name = "dob")
+DT.m1
+```
+
+* De manera predeterminada, cuando falta una de las `id.vars` o `measure.vars`, el resto de las columnas se *asignan automáticamente* al argumento faltante.
+
+* Cuando no se especifican ni `id.vars` ni `measure.vars`, como se menciona en `?melt`, todas las columnas *no* `numeric`, `integer` o `logical` se asignarán a `id.vars`.
+
+ In addition, a warning message is issued highlighting the columns that are automatically considered to be `id.vars`.
+
+### b) `dcast` de `data.table` (de largo a ancho)
+
+En la sección anterior, vimos cómo pasar del formato ancho al formato largo. Veamos la operación inversa en esta sección.
+
+#### - ¿Cómo podemos volver a la tabla de datos original `DT` desde `DT.m1`?
+
+Es decir, queremos recopilar todas las observaciones de *child* correspondientes a cada `family_id, age_mother` en la misma fila. Podemos lograrlo usando `dcast` de la siguiente manera:
+
+```{r}
+dcast(DT.m1, family_id + age_mother ~ child, value.var = "dob")
+```
+
+* `dcast` usa la interfaz *formula*. Las variables del lado izquierdo de *formula* representan las variables *id* y del lado derecho, las variables *measure*.
+
+* `value.var` denota la columna que se debe completar al convertir a formato ancho.
+
+* `dcast` también intenta preservar los atributos en el resultado siempre que sea posible.
+
+#### - A partir de `DT.m1`, ¿cómo podemos obtener el número de hijos en cada familia?
+
+También puede pasar una función para agregar en `dcast` con el argumento `fun.aggregate`. Esto es especialmente esencial cuando la fórmula proporcionada no identifica una sola observación para cada celda.
+
+```{r}
+dcast(DT.m1, family_id ~ ., fun.aggregate = function(x) sum(!is.na(x)), value.var = "dob")
+```
+
+Consulte `?dcast` para obtener otros argumentos útiles y ejemplos adicionales.
+
+## 2. Limitaciones de los enfoques anteriores de `melt/dcast`
+
+Hasta ahora hemos visto características de `melt` y `dcast` que se implementan de manera eficiente para `data.table`s, utilizando maquinaria interna de `data.table` (*ordenamiento rápido de radix*, *búsqueda binaria*, etc.).
+
+Sin embargo, existen situaciones en las que la operación deseada no se expresa de forma clara. Por ejemplo, considere la tabla `data.table` que se muestra a continuación:
+
+```{r}
+s2 <- "family_id age_mother name_child1 name_child2 name_child3 gender_child1 gender_child2 gender_child3
+1 30 Ben Anna NA 1 2 NA
+2 27 Tom NA NA 2 NA NA
+3 26 Lia Sam Amy 2 2 1
+4 32 Max Zoe Joe 1 1 1
+5 29 Dan Eva NA 2 1 NA"
+DT <- fread(s2)
+DT
+## 1 = female, 2 = male
+```
+
+Y podría querer combinar (`melt`) todas las columnas `name` y `gender`. Con la funcionalidad anterior, podríamos hacer algo como esto:
+
+```{r}
+DT.m1 = melt(DT, id.vars = c("family_id", "age_mother"))
+DT.m1[, c("variable", "child") := tstrsplit(variable, "_", fixed = TRUE)]
+DT.c1 = dcast(DT.m1, family_id + age_mother + child ~ variable, value.var = "value")
+DT.c1
+
+str(DT.c1) ## gender column is character type now!
+```
+
+#### Asuntos
+
+1. Lo que queríamos hacer era combinar todas las columnas de tipo `nombre` y `género`, respectivamente. En lugar de eso, combinamos *todo* y luego lo volvemos a dividir. Creo que es fácil ver que es bastante indirecto (e ineficiente).
+
+ 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!
+
+2. Las columnas a fusionar pueden ser de diferentes tipos. Al fusionarlas todas juntas, se forzará el resultado.
+
+3. Generamos una columna adicional dividiendo la columna `variable` en dos, cuyo propósito es bastante complejo. Lo hacemos porque la necesitamos para la *conversión* en el siguiente paso.
+
+4. Finalmente, convertimos el conjunto de datos. El problema es que es una operación mucho más compleja computacionalmente que *melt*. En concreto, requiere calcular el orden de las variables en la fórmula, lo cual es costoso.
+
+De hecho, `stats::reshape` puede realizar esta operación de forma muy sencilla. Es una función extremadamente útil y a menudo subestimada. ¡Debería probarla!
+
+## 3. Funcionalidad mejorada (nueva)
+
+### a) Fusión mejorada
+
+Dado que nos gustaría que `data.table` realice esta operación de manera sencilla y eficiente utilizando la misma interfaz, seguimos adelante e implementamos una *funcionalidad adicional*, donde podemos `fusionar` múltiples columnas *simultáneamente*.
+
+#### - `fundir` múltiples columnas simultáneamente
+
+La idea es bastante sencilla. Pasamos una lista de columnas a `measure.vars`, donde cada elemento de la lista contiene las columnas que deben combinarse.
+
+```{r}
+colA = paste0("name_child", 1:3)
+colB = paste0("gender_child", 1:3)
+DT.m2 = melt(DT, measure.vars = list(colA, colB), value.name = c("name", "gender"))
+DT.m2
+
+str(DT.m2) ## col type is preserved
+```
+
+* Podemos eliminar la columna `variable` si es necesario.
+
+* La funcionalidad está implementada completamente en C y, por lo tanto, es *rápida* y *eficiente en el uso de memoria*, además de ser *sencilla*.
+
+#### - Usando `patrones()`
+
+Normalmente, en estos problemas, las columnas que queremos fundir se distinguen por un patrón común. Podemos usar la función `patterns()`, implementada para mayor comodidad, para proporcionar expresiones regulares que permitan combinar las columnas. La operación anterior se puede reescribir como:
+
+```{r}
+DT.m2 = melt(DT, measure.vars = patterns("^name", "^gender"), value.name = c("name", "gender"))
+DT.m2
+```
+
+#### - Usar `measure()` para especificar `measure.vars` a través de un separador o patrón
+
+Si, como en los datos anteriores, las columnas de entrada que se van a fusionar tienen nombres regulares, podemos usar `measure`, que permite especificar las columnas que se van a fusionar mediante un separador o una expresión regular. Por ejemplo, considere los datos de *iris*:
+
+```{r}
+(two.iris = data.table(datasets::iris)[c(1,150)])
+```
+
+Los datos de *iris* tienen cuatro columnas numéricas con una estructura regular: primero la parte de la flor, luego un punto y finalmente la dimensión de la medida. Para especificar que queremos fusionar esas cuatro columnas, podemos usar `measure` con `sep="."`, lo que significa usar `strsplit` en todos los nombres de columna; las columnas que resulten en el número máximo de grupos después de la división se usarán como `measure.vars`:
+
+```{r}
+melt(two.iris, measure.vars = measure(part, dim, sep="."))
+```
+
+Los primeros dos argumentos de `measure` en el código anterior (`part` y `dim`) 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 `sep`.
+
+Si queremos dos columnas de valores, una para cada parte, podemos usar la palabra clave especial `value.name`, lo que significa generar una columna de valores para cada nombre único encontrado en ese grupo:
+
+```{r}
+melt(two.iris, measure.vars = measure(value.name, dim, sep="."))
+```
+
+Usando el código anterior, obtenemos una columna de valor por cada parte de la flor. Si, en cambio, queremos una columna de valor para cada dimensión de medida, podemos hacer lo siguiente:
+
+```{r}
+melt(two.iris, measure.vars = measure(part, value.name, sep="."))
+```
+
+Volviendo al ejemplo de los datos con familias y niños, podemos ver un uso más complejo de `measure`, que involucra una función que se utiliza para convertir los valores de la cadena `child` en números enteros:
+
+```{r}
+DT.m3 = melt(DT, measure.vars = measure(value.name, child=as.integer, sep="_child"))
+DT.m3
+```
+
+En el código anterior, usamos `sep="_child"`, lo que resulta en la fusión de solo las columnas que contienen esa cadena (seis nombres de columna divididos en dos grupos cada uno). El argumento `child=as.integer` significa que el segundo grupo generará una columna de salida llamada `child` con valores definidos al insertar las cadenas de caracteres de ese grupo en la función `as.integer`.
+
+Finalmente, consideramos un ejemplo (tomado del paquete tidyr) donde necesitamos definir los grupos usando una expresión regular en lugar de un separador.
+
+```{r}
+(who <- data.table(id=1, new_sp_m5564=2, newrel_f65=3))
+melt(who, measure.vars = measure(
+ diagnosis, gender, ages, pattern="new_?(.*)_(.)(.*)"))
+```
+
+Al usar el argumento `patrón`, debe ser una expresión regular compatible con Perl que contenga el mismo número de grupos de captura (subexpresiones entre paréntesis) que el resto de argumentos (nombres de grupo). El código a continuación muestra cómo usar una expresión regular más compleja con cinco grupos, dos columnas de salida numérica y una función de conversión de tipos anónima.
+
+```{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}))"
+))
+```
+
+### b) `dcast` mejorado
+
+¡Genial! Ahora podemos fusionar varias columnas simultáneamente. Dado el conjunto de datos `DT.m2`, como se muestra arriba, ¿cómo podemos recuperar el mismo formato que los datos originales con los que empezamos?
+
+Si usamos la funcionalidad actual de `dcast`, tendríamos que convertir dos veces y enlazar los resultados. Pero esto, una vez más, es demasiado verboso, no es sencillo y, además, ineficiente.
+
+#### - Convertir varios `value.var` simultáneamente
+
+Ahora podemos proporcionar **múltiples columnas `value.var`** a `dcast` para `data.table` directamente para que las operaciones se realicen de manera interna y eficiente.
+
+```{r}
+## new 'cast' functionality - multiple value.vars
+DT.c2 = dcast(DT.m2, family_id + age_mother ~ variable, value.var = c("name", "gender"))
+DT.c2
+```
+
+* Los atributos se conservan en el resultado siempre que sea posible.
+
+* Todo se gestiona internamente y de forma eficiente. Además de ser rápido, también es muy eficiente en el uso de memoria.
+
+#
+
+#### Varias funciones para `fun.aggregate`:
+
+También puede proporcionar múltiples funciones a `fun.aggregate` en `dcast` para *data.tables*. Consulte los ejemplos en `?dcast` que ilustran esta funcionalidad.
+
+```{r, echo=FALSE}
+setDTthreads(.old.th)
+```
+
+#
+
+***
diff --git a/vignettes/es/datatable-sd-usage.Rmd b/vignettes/es/datatable-sd-usage.Rmd
new file mode 100644
index 000000000..e5b6b1524
--- /dev/null
+++ b/vignettes/es/datatable-sd-usage.Rmd
@@ -0,0 +1,262 @@
+---
+title: "Uso de .SD para Análisis de datos"
+date: "`{r} Sys.Date()`"
+output:
+ litedown::html_format:
+ options:
+ toc: true
+ number_sections: true
+vignette: >
+ %\VignetteIndexEntry{Using .SD for Data Analysis}
+ %\VignetteEngine{litedown::vignette}
+ \usepackage[utf8]{inputenc}
+---
+
+
+
+```{r, echo=FALSE, file='../_translation_links.R'}
+```
+
+`{r} .write.translation.links("Las traducciones de este documento están disponibles en: %s")`
+
+```{r, echo = FALSE, message = FALSE}
+library(data.table)
+litedown::reactor(comment = "# ")
+.old.th = setDTthreads(1)
+```
+
+Esta viñeta explica las formas más comunes de usar la variable `.SD` en los análisis de `data.table`. Es una adaptación de [esta respuesta](https://stackoverflow.com/a/47406952/3576984) de StackOverflow.
+
+# ¿Qué es `.SD`?
+
+En sentido amplio, `.SD` es simplemente una abreviatura para capturar una variable que aparece con frecuencia en el contexto del análisis de datos. Puede entenderse como *S*ubset, *S*elfsame o *S*elf-reference of the *D*ata. Es decir, `.SD` es, en su forma más básica, una *referencia reflexiva* a la propia `data.table`; como veremos en los ejemplos a continuación, esto es particularmente útil para encadenar "consultas" (extracciones/subconjuntos/etc. mediante `[`). En particular, esto también significa que `.SD` es *en sí mismo una `data.table`* (con la salvedad de que no permite la asignación con `:=`).
+
+El uso más simple de `.SD` es para la subagrupación de columnas (es decir, cuando se especifica `.SDcols`); dado que esta versión es mucho más sencilla de entender, la abordaremos primero a continuación. La interpretación de `.SD` en su segundo uso, en escenarios de agrupación (es decir, cuando se especifica `by = ` o `keyby = `), es ligeramente diferente conceptualmente (aunque en esencia es la misma, ya que, después de todo, una operación no agrupada es un caso extremo de agrupación con un solo grupo).
+
+## Carga y vista previa de datos de Lahman
+
+Para darle una sensación más realista, en lugar de inventar datos, carguemos algunos conjuntos de datos sobre béisbol desde la [base de datos Lahman](https://github.com/cdalzell/Lahman). En el uso típico de R, simplemente cargaríamos estos conjuntos de datos desde el paquete R `Lahman`; en este ejemplo, los hemos descargado previamente directamente desde la página de GitHub del paquete.
+
+```{r download_lahman}
+load('Teams.RData')
+setDT(Teams)
+Teams
+
+load('Pitching.RData')
+setDT(Pitching)
+Pitching
+```
+
+Los lectores familiarizados con la jerga del béisbol encontrarán el contenido de las tablas familiar: `Teams` registra las estadísticas de un equipo en un año determinado, mientras que `Pitching` registra las estadísticas de un lanzador en un año determinado. Por favor, consulte la documentación (https://github.com/cdalzell/Lahman) y explore los datos usted mismo antes de familiarizarse con su estructura.
+
+# `.SD` en datos no agrupados
+
+Para ilustrar lo que quiero decir sobre la naturaleza reflexiva de `.SD`, considere su uso más banal:
+
+```{r plain_sd}
+Pitching[ , .SD]
+```
+
+Es decir, `Pitching[ , .SD]` simplemente ha devuelto la tabla completa, en otras palabras, era una forma más extensa de escribir `Pitching` o `Pitching[]`:
+
+```{r plain_sd_is_table}
+identical(Pitching, Pitching[ , .SD])
+```
+
+En términos de subconjuntos, `.SD` sigue siendo un subconjunto de los datos, sólo que es trivial (el conjunto en sí).
+
+## Subconjunto de columnas: `.SDcols`
+
+La primera forma de influir en lo que es `.SD` es limitar las *columnas* contenidas en `.SD` usando el argumento `.SDcols` a `[`:
+
+```{r simple_sdcols}
+# W: Wins; L: Losses; G: Games
+Pitching[ , .SD, .SDcols = c('W', 'L', 'G')]
+```
+
+Esto es solo para ilustrar y era bastante aburrido. Además de aceptar un vector de caracteres, `.SDcols` también acepta:
+
+1. cualquier función como `is.character` para filtrar *columnas*
+2. la función[^*] `patterns()` para filtrar *nombres de columnas* por expresión regular
+3. vectores enteros y lógicos
+
+[^*]: consulte `?patterns` para más detalles
+
+Este uso simple se presta a una amplia variedad de operaciones de manipulación de datos altamente beneficiosas y omnipresentes:
+
+## Conversión de tipo de columna
+
+La conversión de tipos de columnas es una práctica habitual en la manipulación de datos. Aunque [`fwrite` ha adquirido recientemente la capacidad de declarar la clase de cada columna por adelantado](https://github.com/Rdatatable/data.table/pull/2545), no todos los conjuntos de datos provienen de `fread` (por ejemplo, en esta viñeta) y las conversiones entre tipos `carácter`/`factor`/`numérico` son comunes. Podemos usar `.SD` y `.SDcols` para convertir por lotes grupos de columnas a un tipo común.
+
+Observamos que las siguientes columnas se almacenan como "carácter" en el conjunto de datos "Equipos", pero podrían almacenarse de manera más lógica como "factor":
+
+```{r identify_factors}
+# teamIDBR: Team ID used by Baseball Reference website
+# teamIDlahman45: Team ID used in Lahman database version 4.5
+# teamIDretro: Team ID used by Retrosheet
+fkt = c('teamIDBR', 'teamIDlahman45', 'teamIDretro')
+# confirm that they're stored as `character`
+str(Teams[ , ..fkt])
+```
+
+La sintaxis para convertir ahora estas columnas a "factor" es simple:
+
+```{r assign_factors}
+Teams[ , names(.SD) := lapply(.SD, factor), .SDcols = patterns('teamID')]
+# print out the first column to demonstrate success
+head(unique(Teams[[fkt[1L]]]))
+```
+
+Nota:
+
+1. El `:=` es un operador de asignación para actualizar `data.table` sin crear una copia. Consulte [`vignette("datatable-reference-semantics", package="data.table")`](datatable-reference-semantics.html) para obtener más información.
+2. El operador izquierdo, `names(.SD)`, indica qué columnas estamos actualizando; en este caso, actualizamos todo el `.SD`.
+3. El operador derecho, `lapply()`, recorre cada columna del `.SD` y la convierte en un factor.
+4. Usamos `.SDcols` para seleccionar solo las columnas cuyo patrón sea `teamID`.
+
+Nuevamente, el argumento `.SDcols` es bastante flexible; anteriormente, proporcionamos `patrones`, pero también podríamos haber proporcionado `fkt` o cualquier vector de `carácter` de nombres de columna. En otras situaciones, es más conveniente proporcionar un vector `entero` de *posiciones* de columna o un vector `lógico` que indique la inclusión/exclusión de cada columna. Finalmente, el uso de una función para filtrar columnas es muy útil.
+
+Por ejemplo, podríamos hacer lo siguiente para convertir todas las columnas de tipo `factor` a `carácter`:
+
+```{r sd_as_logical}
+fct_idx = Teams[, which(sapply(.SD, is.factor))] # column numbers to show the class changing
+str(Teams[[fct_idx[1L]]])
+Teams[ , names(.SD) := lapply(.SD, as.character), .SDcols = is.factor]
+str(Teams[[fct_idx[1L]]])
+```
+
+Por último, podemos hacer una selección por coincidencia de patrones en `.SDcols` para seleccionar todas las columnas que contienen `team` hasta `factor`:
+
+```{r sd_patterns}
+Teams[ , .SD, .SDcols = patterns('team')]
+Teams[ , names(.SD) := lapply(.SD, factor), .SDcols = patterns('team')]
+```
+
+** Una salvedad a lo anterior: usar números de columna *explícitamente* (como `DT[ , (1) := rnorm(.N)]`) es una mala práctica y puede provocar la corrupción silenciosa del código con el tiempo si cambian las posiciones de las columnas. Incluso usar números implícitamente puede ser peligroso si no mantenemos un control estricto sobre el orden en que creamos y usamos el índice numerado.
+
+## Controlar el lado derecho de un modelo
+
+Variar la especificación del modelo es una característica fundamental del análisis estadístico robusto. Intentemos predecir la efectividad (Earned Runs Average, una medida de rendimiento) de un lanzador utilizando el pequeño conjunto de covariables disponibles en la tabla "Lanzamiento". ¿Cómo varía la relación (lineal) entre "W" (victorias) y "ERA" según las otras covariables incluidas en la especificación?
+
+A continuación se muestra un breve script que aprovecha el poder de `.SD` y que explora esta pregunta:
+
+```{r sd_for_lm, cache = FALSE, fig.cap="Fit OLS coefficient on W, various specifications, depicted as bars with distinct colors."}
+# this generates a list of the 2^k possible extra variables
+# for models of the form ERA ~ G + (...)
+extra_var = c('yearID', 'teamID', 'G', 'L')
+models = unlist(
+ lapply(0L:length(extra_var), combn, x = extra_var, simplify = FALSE),
+ recursive = FALSE
+)
+
+# here are 16 visually distinct colors, taken from the list of 20 here:
+# https://sashat.me/2017/01/11/list-of-20-simple-distinct-colors/
+col16 = c('#e6194b', '#3cb44b', '#ffe119', '#0082c8',
+ '#f58231', '#911eb4', '#46f0f0', '#f032e6',
+ '#d2f53c', '#fabebe', '#008080', '#e6beff',
+ '#aa6e28', '#fffac8', '#800000', '#aaffc3')
+
+par(oma = c(2, 0, 0, 0))
+lm_coef = sapply(models, function(rhs) {
+ # using ERA ~ . and data = .SD, then varying which
+ # columns are included in .SD allows us to perform this
+ # iteration over 16 models succinctly.
+ # coef(.)['W'] extracts the W coefficient from each model fit
+ Pitching[ , coef(lm(ERA ~ ., data = .SD))['W'], .SDcols = c('W', rhs)]
+})
+barplot(lm_coef, names.arg = sapply(models, paste, collapse = '/'),
+ main = 'Wins Coefficient\nWith Various Covariates',
+ col = col16, las = 2L, cex.names = 0.8)
+```
+
+El coeficiente siempre tiene el signo esperado (los mejores lanzadores tienden a tener más victorias y menos carreras permitidas), pero la magnitud puede variar sustancialmente dependiendo de qué más controlemos.
+
+## Uniones condicionales
+
+La sintaxis de `data.table` es atractiva por su simplicidad y robustez. La sintaxis `x[i]` maneja con flexibilidad tres enfoques comunes para la creación de subconjuntos: cuando `i` es un vector `lógico`, `x[i]` devolverá las filas de `x` correspondientes a donde `i` es `VERDADERO`; cuando `i` es *otro `data.table`* (o una `lista`), se realiza una `join` (derecha) (en formato simple, usando las `key` de `x` e `i`; de lo contrario, cuando se especifica `on = `, usando las coincidencias de esas columnas); y cuando `i` es un carácter, se interpreta como una abreviatura de `x[list(i)]`, es decir, como una unión.
+
+Esto es excelente en general, pero se queda corto cuando deseamos realizar una *unión condicional*, en donde la naturaleza exacta de la relación entre tablas depende de algunas características de las filas en una o más columnas.
+
+Este ejemplo es ciertamente un poco artificial, pero ilustra la idea; consulte aquí ([1](https://stackoverflow.com/questions/31329939/conditional-keyed-join-update-and-update-a-flag-column-for-matches), [2](https://stackoverflow.com/questions/29658627/conditional-binary-join-and-update-by-reference-using-the-data-table-package)) para obtener más información.
+
+El objetivo es agregar una columna `team_performance` a la tabla `Pitching` que registre el desempeño (ranking) del mejor lanzador de cada equipo (medido por la ERA más baja, entre los lanzadores con al menos 6 juegos registrados).
+
+```{r conditional_join}
+# to exclude pitchers with exceptional performance in a few games,
+# subset first; then define rank of pitchers within their team each year
+# (in general, we should put more care into the 'ties.method' of frank)
+Pitching[G > 5, rank_in_team := frank(ERA), by = .(teamID, yearID)]
+Pitching[rank_in_team == 1, team_performance :=
+ Teams[.SD, Rank, on = c('teamID', 'yearID')]]
+```
+
+Tenga en cuenta que la sintaxis `x[y]` devuelve valores `nrow(y)` (es decir, es una unión derecha), por lo que `.SD` está a la derecha en `Teams[.SD]` (ya que el RHS de `:=` en este caso requiere valores `nrow(Pitching[rank_in_team == 1])`).
+
+# Operaciones agrupadas `.SD`
+
+A menudo, nos gustaría realizar alguna operación con nuestros datos *a nivel de grupo*. Cuando especificamos `by =` (o `keyby = `), el modelo mental de lo que sucede cuando `data.table` procesa `j` es pensar que `data.table` está dividido en varios sub`data.table` componentes, cada uno de los cuales corresponde a un único valor de la(s) variable(s) `by`:
+
+
+
+
+
+En el caso de la agrupación, `.SD` es de naturaleza múltiple: se refiere a *cada* una de estas sub-`data.table`s, *una a la vez* (para ser más precisos, el alcance de `.SD` es una sola sub-`data.table`). Esto nos permite expresar concisamente la operación que queremos realizar en *cada sub-`data.table`* antes de que se nos devuelva el resultado reensamblado.
+
+Esto es útil en una variedad de configuraciones, las más comunes de las cuales se presentan aquí:
+
+## Subconjunto de grupo
+
+Obtengamos los datos de la temporada más reciente de cada equipo en los datos de Lahman. Esto se puede hacer de forma sencilla con:
+
+```{r group_sd_last}
+# the data is already sorted by year; if it weren't
+# we could do Teams[order(yearID), .SD[.N], by = teamID]
+Teams[ , .SD[.N], by = teamID]
+```
+
+Recuerde que `.SD` es en sí mismo una `data.table`, y que `.N` se refiere al número total de filas en un grupo (es igual a `nrow(.SD)` dentro de cada grupo), por lo que `.SD[.N]` devuelve la *totalidad de `.SD`* para la fila final asociada con cada `teamID`.
+
+Otra versión común de esto es utilizar `.SD[1L]` para obtener la *primera* observación para cada grupo, o `.SD[sample(.N, 1L)]` para devolver una fila *aleatoria* para cada grupo.
+
+## Grupo Optima
+
+Supongamos que quisiéramos devolver el *mejor* año de cada equipo, medido por su número total de carreras anotadas (`R`; podríamos ajustar esto fácilmente para referirnos a otras métricas, por supuesto). En lugar de tomar un elemento *fijo* de cada sub`data.table`, ahora definimos el índice deseado *dinámicamente* de la siguiente manera:
+
+```{r sd_team_best_year}
+Teams[ , .SD[which.max(R)], by = teamID]
+```
+
+Tenga en cuenta que este enfoque, por supuesto, se puede combinar con `.SDcols` para devolver solo partes de `data.table` para cada `.SD` (con la salvedad de que `.SDcols` debe fijarse en los distintos subconjuntos).
+
+*NB*: `.SD[1L]` está actualmente optimizado por [*`GForce`*](https://Rdatatable.gitlab.io/data.table/library/data.table/html/datatable-optimize.html) ([ver también](https://stackoverflow.com/questions/22137591/about-gforce-in-data-table-1-9-2)), los elementos internos de `data.table` que aceleran enormemente las operaciones agrupadas más comunes como `sum` o `mean` - ver `?GForce` para más detalles y estar atento/voz de soporte para solicitudes de mejoras de características para actualizaciones en este frente: [1](https://github.com/Rdatatable/data.table/issues/735), [2](https://github.com/Rdatatable/data.table/issues/2778), [3](https://github.com/Rdatatable/data.table/issues/523), [4](https://github.com/Rdatatable/data.table/issues/971), [5](https://github.com/Rdatatable/data.table/issues/1197), [6](https://github.com/Rdatatable/data.table/issues/1414).
+
+## Regresión agrupada
+
+Volviendo a la pregunta anterior sobre la relación entre `ERA` y `W`, supongamos que esperamos que esta relación varíe según el equipo (es decir, que cada equipo tiene una pendiente diferente). Podemos volver a ejecutar fácilmente esta regresión para explorar la heterogeneidad de esta relación de la siguiente manera (teniendo en cuenta que los errores estándar de este enfoque suelen ser incorrectos; la especificación `ERA ~ W*teamID` será mejor; este enfoque es más fácil de leer y los *coeficientes* son correctos):
+
+```{r group_lm, results = 'hide', fig.cap="A histogram depicting the distribution of fitted coefficients. It is vaguely bell-shaped and concentrated around -.2"}
+# Overall coefficient for comparison
+overall_coef = Pitching[ , coef(lm(ERA ~ W))['W']]
+# use the .N > 20 filter to exclude teams with few observations
+Pitching[ , if (.N > 20L) .(w_coef = coef(lm(ERA ~ W))['W']), by = teamID
+ ][ , hist(w_coef, 20L, las = 1L,
+ xlab = 'Fitted Coefficient on W',
+ ylab = 'Number of Teams', col = 'darkgreen',
+ main = 'Team-Level Distribution\nWin Coefficients on ERA')]
+abline(v = overall_coef, lty = 2L, col = 'red')
+```
+
+Si bien es cierto que existe un grado considerable de heterogeneidad, hay una clara concentración en torno al valor general observado.
+
+Lo anterior es solo una breve introducción del poder de `.SD` para facilitar un código hermoso y eficiente en `data.table`.
+
+```{r, echo=FALSE}
+setDTthreads(.old.th)
+```
diff --git a/vignettes/es/datatable-secondary-indices-and-auto-indexing.Rmd b/vignettes/es/datatable-secondary-indices-and-auto-indexing.Rmd
new file mode 100644
index 000000000..5f02f03ed
--- /dev/null
+++ b/vignettes/es/datatable-secondary-indices-and-auto-indexing.Rmd
@@ -0,0 +1,364 @@
+---
+title: "Índices secundarios y auto indexación"
+date: "`{r} Sys.Date()`"
+output:
+ litedown::html_format
+vignette: >
+ %\VignetteIndexEntry{Secondary indices and auto indexing}
+ %\VignetteEngine{litedown::vignette}
+ \usepackage[utf8]{inputenc}
+---
+
+```{r, echo=FALSE, file='../_translation_links.R'}
+```
+
+`{r} .write.translation.links("Las traducciones de este documento están disponibles en: %s")`
+
+```{r, echo = FALSE, message = FALSE}
+library(data.table)
+litedown::reactor(comment = "# ")
+.old.th = setDTthreads(1)
+```
+
+Esta viñeta asume que el lector está familiarizado con la sintaxis `[i, j, by]` de data.table y cómo crear subconjuntos rápidos basados en claves. Si no está familiarizado con estos conceptos, lea primero las siguientes viñetas:
+
+- [`viñeta("datatable-intro", paquete="data.table")`](datatable-intro.html)
+- [`viñeta("datatable-reference-semantics", paquete="data.table")`](datatable-reference-semantics.html)
+- [`viñeta("datatable-keys-fast-subset", paquete="data.table")`](datatable-keys-fast-subset.html)
+
+***
+
+## Datos {#data}
+
+Utilizaremos los mismos datos de `flights` que en la viñeta [`vignette("datatable-intro", package="data.table")`](datatable-intro.html).
+
+```{r, echo = FALSE}
+options(width = 100L)
+```
+
+```{r}
+flights <- fread("../flights14.csv")
+head(flights)
+dim(flights)
+```
+
+## Introducción
+
+En esta viñeta, vamos a:
+
+* analizar los *índices secundarios* y justificar por qué los necesitamos citando casos en los que configurar claves no es necesariamente ideal,
+
+* realizar filtros rápidos, una vez más, pero usando el nuevo argumento `on`, que calcula índices secundarios internamente para la tarea (temporalmente) y reutiliza si ya existe uno,
+
+* y finalmente veremos la *indexación automática* que va un paso más allá y crea índices secundarios automáticamente, pero lo hace en sintaxis nativa de R para filtrar.
+
+## 1. Índices secundarios
+
+### a) ¿Qué son los índices secundarios?
+
+Los índices secundarios son similares a las "claves" en *data.table*, excepto por dos diferencias importantes:
+
+* No reordena físicamente toda la tabla data.table en RAM. En su lugar, solo calcula el orden del conjunto de columnas proporcionado y almacena ese vector de orden en un atributo adicional llamado `index`.
+
+* Puede haber más de un índice secundario para una tabla de datos (como veremos a continuación).
+
+#### Subconjuntos con clave vs. subconjuntos indexados
+
+Si bien tanto las **claves** como los **índices** permiten la subdivisión rápida de búsquedas binarias, difieren significativamente en su uso:
+
+**filtrado mediante clave** (coincidencia de columnas implícita)
+
+```{r keyed_operations}
+DT = data.table(a = c(TRUE, FALSE), b = 1:2)
+setkey(DT, a) # Set key, reordering DT
+DT[.(TRUE)] # 'on' is optional; if omitted, the key is used
+```
+
+**Filtrado mediante índice** (especificación de columna explícita)
+
+```{r unkeyed_operations}
+DT = data.table(a = c(TRUE, FALSE), b = 1:2)
+setindex(DT, a) # Set index only (no reorder)
+DT[.(TRUE), on = "a"] # 'on' is required
+```
+
+### b) Establecer y obtener índices secundarios
+
+#### -- ¿Cómo podemos establecer la columna `origin` como un índice secundario en la *tabla de datos* `vuelos`?
+
+```{r}
+setindex(flights, origin)
+head(flights)
+
+## alternatively we can provide character vectors to the function 'setindexv()'
+# setindexv(flights, "origin") # useful to program with
+
+# 'index' attribute added
+names(attributes(flights))
+```
+
+* `setindex` y `setindexv()` permiten agregar un índice secundario a la tabla de datos.
+
+* Tenga en cuenta que `flights` **no** se reordena físicamente en orden creciente de `origen`, como habría sido el caso con `setkey()`.
+
+* Tenga en cuenta también que se ha añadido el atributo `index` a `flights`.
+
+* `setindex(flights, NULL)` eliminaría todos los índices secundarios.
+
+#### -- ¿Cómo podemos obtener todos los índices secundarios establecidos hasta ahora en `vuelos`?
+
+```{r}
+indices(flights)
+
+setindex(flights, origin, dest)
+indices(flights)
+```
+
+* La función `indices()` devuelve todos los índices secundarios actuales en la tabla data.table. Si no existe ninguno, se devuelve `NULL`.
+
+* Nótese que al crear otro índice en las columnas `origin, dest`, no perdemos el primer índice creado en la columna `origin`, es decir, podemos tener múltiples índices secundarios.
+
+### c) ¿Por qué necesitamos índices secundarios?
+
+#### -- Reordenar una tabla de datos puede ser costoso y no siempre ideal.
+
+Considere el caso en el que desea realizar un subconjunto rápido basado en clave en la columna `origen` para el valor "JFK". Lo haríamos así:
+
+```r
+## not run
+setkey(flights, origin)
+flights["JFK"] # or flights[.("JFK")]
+```
+
+#### `setkey()` requiere:
+
+a) calcular el vector de orden para la(s) columna(s) proporcionada(s), aquí, `origen`, y
+
+b) reordenar toda la tabla de datos, por referencia, en función del vector de orden calculado.
+
+#
+
+Calcular el orden no es la parte más laboriosa, ya que data.table utiliza un ordenamiento por radix real en vectores enteros, de caracteres y numéricos. Sin embargo, reordenar data.table podría requerir mucho tiempo (dependiendo del número de filas y columnas).
+
+A menos que nuestra tarea implique la subconfiguración repetida de la misma columna, la subconfiguración rápida basada en clave podría anularse efectivamente al momento de reordenar, dependiendo de las dimensiones de nuestra tabla de datos.
+
+#### -- Solo puede haber una `clave` como máximo
+
+Ahora, si quisiéramos repetir la misma operación pero en la columna `dest`, para el valor "LAX", entonces tenemos que usar `setkey()`, *nuevamente*.
+
+```r
+## not run
+setkey(flights, dest)
+flights["LAX"]
+```
+
+Y esto reordena `flights` por `dest`, *de nuevo*. Lo que realmente nos gustaría es poder realizar el filtrado rápido eliminando el paso de reordenación.
+
+¡Y esto es precisamente lo que permiten los *índices secundarios*!
+
+#### -- Los índices secundarios se pueden reutilizar
+
+Dado que puede haber múltiples índices secundarios, y crear un índice es tan simple como almacenar el vector de orden como un atributo, esto nos permite incluso eliminar el tiempo para volver a calcular el vector de orden si ya existe un índice.
+
+#### -- El nuevo argumento `on` permite una sintaxis más limpia y la creación y reutilización automática de índices secundarios.
+
+Como veremos en la siguiente sección, el argumento `on` proporciona varias ventajas:
+
+#### argumento `on`
+
+* Permite la creación de subconjuntos calculando índices secundarios sobre la marcha. Esto elimina la necesidad de ejecutar `setindex()` cada vez.
+
+* permite la reutilización sencilla de índices existentes simplemente verificando los atributos.
+
+* permite una sintaxis más clara al incluir las columnas en las que se ejecuta el subconjunto como parte de la sintaxis. Esto facilita la lectura del código al revisarlo posteriormente.
+
+ Note that `on` argument can also be used on keyed subsets as well. In fact, we encourage providing the `on` argument even when subsetting using keys for better readability.
+
+#
+
+## 2. Subconjunto rápido utilizando el argumento `on` e índices secundarios
+
+### a) Subconjuntos rápidos en `i`
+
+#### -- Subconjunto de todas las filas donde el aeropuerto de origen coincide con *"JFK"* usando `on`
+
+```{r}
+flights["JFK", on = "origin"]
+
+## alternatively
+# flights[.("JFK"), on = "origin"] (or)
+# flights[list("JFK"), on = "origin"]
+```
+
+* Esta instrucción también realiza una búsqueda binaria rápida basada en subconjuntos, calculando el índice sobre la marcha. Sin embargo, tenga en cuenta que no guarda el índice como atributo automáticamente. Esto podría cambiar en el futuro.
+
+* Si ya hubiéramos creado un índice secundario con `setindex()`, `on` lo reutilizaría en lugar de recalcularlo. Podemos comprobarlo con `verbose = TRUE`:
+
+ ```{r}
+ setindex(flights, origin)
+ flights["JFK", on = "origin", verbose = TRUE][1:5]
+ ```
+
+#### -- ¿Cómo puedo filtrar en función de las columnas `origin` *y* `dest`?
+
+Por ejemplo, si queremos filtrar la combinación `"JFK", "LAX"`, entonces:
+
+```{r}
+flights[.("JFK", "LAX"), on = c("origin", "dest")][1:5]
+```
+
+* El argumento `on` acepta un vector de caracteres de nombres de columnas correspondientes al orden proporcionado a `i-argument`.
+
+* Dado que el tiempo para calcular el índice secundario es bastante pequeño, no tenemos que usar `setindex()`, a menos que, una vez más, la tarea implique filtros repetidos en la misma columna.
+
+* Para mayor claridad y legibilidad, podría ser útil nombrar las entradas en `i`, por ejemplo,
+
+```{r}
+flights[.(origin = "JFK", dest = "LAX"), on = c("origin", "dest")]
+```
+
+Esto deja claro qué entradas en `j` corresponden a qué elemento de `on`.
+
+### b) Seleccionar en `j`
+
+Todas las operaciones que analizaremos a continuación son idénticas a las que ya vimos en la viñeta [`vignette("datatable-keys-fast-subset", package="data.table")`](datatable-keys-fast-subset.html). Excepto que usaremos el argumento `on` en lugar de establecer las claves.
+
+#### -- Devuelve la columna `arr_delay` sola como una tabla de datos correspondiente a `origin = "LGA"` y `dest = "TPA"`
+
+```{r}
+flights[.("LGA", "TPA"), .(arr_delay), on = c("origin", "dest")]
+```
+
+### c) Encadenamiento
+
+#### -- Sobre el resultado obtenido anteriormente, utilice el encadenamiento para ordenar la columna en orden decreciente.
+
+```{r}
+flights[.("LGA", "TPA"), .(arr_delay), on = c("origin", "dest")][order(-arr_delay)]
+```
+
+### d) Calcular o *hacer* en `j`
+
+#### -- Encuentra el retraso máximo de llegada correspondiente a `origin = "LGA"` y `dest = "TPA"`.
+
+```{r}
+flights[.("LGA", "TPA"), max(arr_delay), on = c("origin", "dest")]
+```
+
+### e) *sub-asignar* por referencia usando `:=` en `j`
+
+Ya hemos visto este ejemplo en las viñetas [`vignette("datatable-reference-semantics", package="data.table")`](datatable-reference-semantics.html) y [`vignette("datatable-keys-fast-subset", package="data.table")`](datatable-keys-fast-subset.html). Veamos todas las horas disponibles en la tabla de datos `flights`:
+
+```{r}
+# get all 'hours' in flights
+flights[, sort(unique(hour))]
+```
+
+Observamos que hay un total de 25 valores únicos en los datos. Parece que hay tanto *0* como *24* horas. Reemplacemos *24* por *0*, pero esta vez usando `on` en lugar de las claves de configuración.
+
+```{r}
+flights[.(24L), hour := 0L, on = "hour"]
+```
+
+Ahora, verifiquemos si `24` se reemplaza con `0` en la columna `hora`.
+
+```{r}
+flights[, sort(unique(hour))]
+```
+
+* Esta es una gran ventaja de los índices secundarios. Anteriormente, para actualizar solo algunas filas de `hour`, teníamos que ejecutar `setkey()`, lo que inevitablemente reordenaba toda la tabla data.table. Con `on`, el orden se conserva y la operación es mucho más rápida. Al observar el código, la tarea que queríamos realizar también está bastante clara.
+
+### f) Agregación utilizando `by`
+
+#### Obtener el retraso máximo de salida para cada mes correspondiente a «origen = "JFK"». Ordenar el resultado por `month`.
+
+```{r}
+ans <- flights["JFK", max(dep_delay), keyby = month, on = "origin"]
+head(ans)
+```
+
+* Tendríamos que haber establecido la `clave` nuevamente en `origin, dest`, si no hubiéramos usado `on` que construye internamente índices secundarios sobre la marcha.
+
+### g) El argumento *mult*
+
+Los demás argumentos, incluido `mult`, funcionan exactamente igual que en la viñeta [`vignette("datatable-keys-fast-subset", package="data.table")`](datatable-keys-fast-subset.html). El valor predeterminado para `mult` es "all". Podemos elegir; en su lugar, solo se devolverán las *primeras* (`mult = "first"`) o *últimas* (`mult = "last"`) filas coincidentes.
+
+#### -- Subconjunto solo de la primera fila coincidente donde `dest` coincide con *"BOS"* y *"DAY"*
+
+```{r}
+flights[c("BOS", "DAY"), on = "dest", mult = "first"]
+```
+
+#### -- Filtrar solo la última fila coincidente donde `origin` coincide con *"LGA", "JFK", "EWR"* y `dest` coincide con *"XNA"*
+
+```{r}
+flights[.(c("LGA", "JFK", "EWR"), "XNA"), on = c("origin", "dest"), mult = "last"]
+```
+
+### h) El argumento *nomatch*
+
+Podemos elegir si las consultas que no coinciden deben devolver "NA" o ignorarse por completo utilizando el argumento "nomatch".
+
+#### -- Del ejemplo anterior, filtre todas las filas solo si hay una coincidencia
+
+```{r}
+flights[.(c("LGA", "JFK", "EWR"), "XNA"), mult = "last", on = c("origin", "dest"), nomatch = NULL]
+```
+
+* No hay vuelos que conecten "JFK" y "XNA". Por lo tanto, esa fila se omite en el resultado.
+
+## 3. Indexación automática
+
+Primero, vimos cómo crear subconjuntos rápidos mediante búsqueda binaria con *claves*. Luego, descubrimos que podíamos mejorar aún más el rendimiento y lograr una sintaxis más clara usando índices secundarios.
+
+Eso es lo que hace la *autoindexación*. Actualmente, solo está implementada para los operadores binarios `==` y `%in%`. Se crea automáticamente un índice *y* se guarda como atributo. Es decir, a diferencia del argumento `on`, que calcula el índice sobre la marcha cada vez (a menos que ya exista uno), aquí se crea un índice secundario.
+
+Comencemos creando una tabla de datos lo suficientemente grande para resaltar la ventaja.
+
+```{r}
+set.seed(1L)
+dt = data.table(x = sample(1e5L, 1e7L, TRUE), y = runif(100L))
+print(object.size(dt), units = "Mb")
+```
+
+Cuando usamos `==` o `%in%` en una sola columna por primera vez, se crea automáticamente un índice secundario y se utiliza para filtrar.
+
+```{r}
+## have a look at all the attribute names
+names(attributes(dt))
+
+## run thefirst time
+(t1 <- system.time(ans <- dt[x == 989L]))
+head(ans)
+
+## secondary index is created
+names(attributes(dt))
+
+indices(dt)
+```
+
+El tiempo para crear el subconjunto la primera vez equivale al tiempo para crear el índice + el tiempo para crear el subconjunto. Dado que crear un índice secundario solo implica crear el vector de orden, esta operación combinada es más rápida que los escaneos vectoriales en muchos casos. Pero la verdadera ventaja reside en los filtrados sucesivos, ya que son extremadamente rápidos.
+
+```{r}
+## successive subsets
+(t2 <- system.time(dt[x == 989L]))
+system.time(dt[x %in% 1989:2012])
+```
+
+* La primera ejecución tardó `r sprintf("%.3f", t1["elapsed"])` segundos, mientras que la segunda vez tardó `r sprintf("%.3f", t2["elapsed"])` segundos.
+
+* La indexación automática se puede desactivar configurando el argumento global `options(datatable.auto.index = FALSE)`.
+
+* Deshabilitar la indexación automática permite seguir usando índices creados explícitamente con `setindex` o `setindexv`. Puede deshabilitar los índices por completo configurando el argumento global `options(datatable.use.index = FALSE)`.
+
+#
+
+En la versión reciente, ampliamos la indexación automática a expresiones que involucran más de una columna (combinadas con el operador `&`). En el futuro, planeamos ampliar la búsqueda binaria para que funcione con más operadores binarios como `<`, `<=`, `>` y `>=`.
+
+Discutiremos *filtros* rápidos usando claves e índices secundarios para *uniones* (join) en la [siguiente viñeta (`vignette("datatable-joins", package="data.table")`)](datatable-joins.html).
+
+***
+
+```{r, echo=FALSE}
+setDTthreads(.old.th)
+```