Está en la página 1de 107

UN RECORRIDO POR LA CIENCIA DE LOS

DATOS
Serie de datos grandes de Chapman & Hall/CRC

Editor de la serie: Sanjay Ranka

Un recorrido por la ciencia de datos


Aprenda R y Python en paralelo
Nailon Zhang

Informática de grandes datos


Una guía para gerentes de negocios y tecnología
Vivek Kale

Gestión y procesamiento de Big Data


Kuan Ching Li, Hai Jiang, Albert Y. Zomaya

Fronteras en la ciencia de datos


Matthias Dehmer, Frank EmmertStreib

Informática de alto rendimiento para Big Data


Metodologías y Aplicaciones
chao wang
Análisis de grandes datos
Herramientas y Tecnología para una Planificación Efectiva
Arun K. Somani, Ganesh Chandra Deka
Datos inteligentes
Perspectivas de vanguardia en computación y aplicaciones
KuanChing Li, Qingchen Zhang, Laurence T. Yang, Beniamino Di Martino

Internet de las Cosas y Entornos Inteligentes Seguros

Éxitos y trampas
Uttam Ghosh, Danda B. Rawat, Raja Datta, AlSakib Khan Pathan

Para obtener más información sobre esta serie, visite:


https://www.crcpress.com/ChapmanHallCRCBigData Series/bookseries/CRCBIGDATSER
Un recorrido por la ciencia de datos
Aprenda R y Python en paralelo

Nailon Zhang
Primera edición publicada en 2021 por
CRC Press
6000 Broken Sound Parkway NW, Suite 300, Boca Ratón, FL 334872742

y por CRC Press


2 Park Square, Milton Park, Abingdon, Oxon, OX14 4RN
© 2021 Grupo Taylor & Francis, LLC

CRC Press es un sello de Taylor & Francis Group, LLC


Se han realizado esfuerzos razonables para publicar datos e información confiables, pero el autor y el editor no pueden
asumir responsabilidad por la validez de todos los materiales o las consecuencias de su uso. Los autores y editores han
intentado localizar a los titulares de los derechos de autor de todo el material reproducido en esta publicación y se
disculpan con los titulares de los derechos de autor si no se ha obtenido el permiso para publicar de esta forma. Si algún
material con derechos de autor no ha sido reconocido, por favor escríbanos y háganoslo saber para que podamos
rectificar en cualquier reimpresión futura.

Salvo que lo permita la Ley de derechos de autor de EE. UU., ninguna parte de este libro puede reimprimirse,
reproducirse, transmitirse o utilizarse de ninguna forma por ningún medio electrónico, mecánico o de otro tipo, ahora
conocido o inventado en el futuro, incluidas las fotocopias, microfilmaciones y grabaciones. , o en cualquier sistema de
almacenamiento o recuperación de información, sin el permiso por escrito de los editores.

Para obtener permiso para fotocopiar o usar material electrónico de este trabajo, acceda a www. copyright.com o
comuníquese con Copyright Clearance Center, Inc. (CCC), 222 Rosewood Drive, Danvers, MA 01923, 9787508400.
Para obras que no están disponibles en CCC, póngase en contacto con mpkbookspermissions@tandf.co.uk

Aviso de marca comercial: los nombres de productos o empresas pueden ser marcas comerciales o marcas comerciales
registradas y se usan solo para identificación y explicación sin intención de infringir.

Datos de catalogación en publicación de la Biblioteca del Congreso


Nombres: Zhang, Nailong, autor.

Título: Un recorrido por la ciencia de datos: aprenda R y Python en paralelo / Nailong Zhang. Descripción: Primera
edición. | Abingdon, Oxón ; Boca Ratón, FL: CRC Press, 2021. |

Serie: Serie de ciencia de datos de Chapman & Hall/CRC | Incluye referencias bibliográficas e indice.

Identificadores: LCCN 2020025166 | ISBN 9780367895860 (rústica) | ISBN 9780367897062 (tapa dura) | ISBN
9781003020646 (libro electrónico)

Materias: LCSH: Minería de datos. | R (Lenguaje de programa informático) | Python (lenguaje de programa informático)

Clasificación: LCC QA76.9.D343 Z458 2021 | DDC 006.3/12dc23 Registro LC disponible en


https://lccn.loc.gov/2020025166

ISBN: 9780367895860 (hbk)

Compuesto en fuente Computer Modern


Por Cenveo Publisher Services
A mis padres
Contenido
Prefacio ix
1 SUPOSICIONES SOBRE LOS ANTECEDENTES DEL LECTOR
ix

2 RESUMEN DEL LIBRO ix

Capítulo 1 • Introducción a la programación en R/Python

I. CALCULADORA 1
II. VARIABLE Y TIPO 3
III. FUNCIONES 4
IV. FLUJOS DE CONTROL 6
V. ALGUNAS ESTRUCTURAS DE DATOS INTEGRADAS 9
VI. REVISITA DE VARIABLES 21

Capítulo 2 • Más sobre programación en R/Python

1 TRABAJAR CON SCRIPTS R/PYTHON


2 DEPURACIÓN EN R/PYTHON
3 COMPARACIÓN
4 VECTORIZACIÓN
5 PARALELISMO VERGONZOSAMENTE EN R/PYTHON
6 ESTRATEGIA DE EVALUACIÓN
7 ACELERA CON C/C++ EN R/PYTHON
8 UNA PRIMERA IMPRESIÓN DE LA PROGRAMACIÓN FUNCIONAL
9 MISCELÁNEAS

Capítulo 3 • tabla de datos y pandas


1 SQL
2 COMIENCE CON DATA.TABLE Y PANDAS
3
4 INDEXACIÓN Y SELECCIÓN DE DATOS 76
5 AGREGAR/ELIMINAR/ACTUALIZAR 86
6 AGRUPAR POR 91
7 UNIRSE
Capítulo 4 • Variables aleatorias, distribuciones y regresión lineal
1 UN ACTUALIZADOR SOBRE LAS DISTRIBUCIONES 99
2 MUESTREO POR INVERSIÓN Y RECHAZO MUESTREO 102
3 DISTRIBUCIÓN CONJUNTA Y CÓPULA 106
4 AJUSTAR UNA DISTRIBUCIÓN 110
5 INTERVALO DE CONFIANZA 111
6 EVALUACIÓN DE LA HIPÓTESIS 114
7 FUNDAMENTOS DE LA REGRESIÓN LINEAL 122
8 REGRESIÓN DE LA CRESTA 127
Capítulo 5 • Optimización en la práctica
1 CONVEXIDAD 133
2 DESCENSO DE GRADIENTE 134
3 BÚSQUEDA DE RAÍCES 141
4 HERRAMIENTAS DE MINIMIZACIÓN DE PROPÓSITO GENERAL EN R/PITON 143
5 PROGRAMACIÓN LINEAL 149
6 MISCELÁNEAS 153

Capítulo 6 • Aprendizaje automático Una introducción suave


1 APRENDIZAJE SUPERVISADO 161
2 MÁQUINA IMPULSADORA DE GRADIENTE 169
3 APRENDIZAJE NO SUPERVISADO 188
4 APRENDIZAJE POR REFUERZO 195
5 REDES Q PROFUNDAS 197
6 DIFERENCIACIÓN COMPUTACIONAL 200
7 VARIOS 202
Bibliografía 203
Índice 205
PREFACIO
0.1 SUPOSICIONES SOBRE LOS ANTECEDENTES DEL LECTOR
Es difícil dar una definición clara de ciencia de datos porque no está claro dónde está el límite. En
realidad, muchos científicos de datos trabajan en diferentes industrias con diferentes conjuntos de
habilidades. En términos generales, la ciencia de datos requiere un amplio conocimiento en
estadística, aprendizaje automático, optimización y programación. Es imposible cubrir siquiera una
de estas cuatro áreas en profundidad en un libro corto, y también está más allá del alcance de este
libro. Sin embargo, creo que podría ser útil tener un libro que hable sobre ciencia de datos con una
amplia gama de temas y una cantidad moderada de detalles técnicos. Y esa es una de las
motivaciones para escribir este libro. Elegí hacer el libro breve con la introducción de teorías
matemáticas mínimas, y espero que leer el libro pueda ayudar a los lectores a tener una idea de lo
que es la ciencia de datos.

Supongo que los lectores tienen conocimientos básicos de estadística y álgebra lineal y
cálculo, como distribución normal, tamaño de muestra, gradiente, inversión de matriz, etc.
No se requieren conocimientos previos de programación. Sin embargo, como dice el título del libro,
aprenderemos R y Python en paralelo, si ya está familiarizado con R o Python, le ayudaría a
aprender el otro en función de una comparación de lado a lado.
Además de la comparación de los dos lenguajes populares utilizados en la ciencia de datos,
este libro también se enfoca en la traducción de modelos matemáticos a códigos. En el libro, la
audiencia pudo encontrar aplicaciones/implementaciones de algunos algoritmos importantes desde
cero, como estimación de máxima verosimilitud, muestreo de inversión, simulación de cópula,
recocido simulado, arranque, regresión lineal (regresión de lazo/cresta), regresión logística,
aumento de gradiente árboles, etc
0.2 RESUMEN DEL LIBRO

El libro consta de seis capítulos diseñados para introducir ideas centrales en la ciencia de datos.
Capítulo 1: Introducción a la programación de R/Python : R y Python son los dos lenguajes de
programación más populares utilizados en la ciencia de datos. En este capítulo, repasamos los
conceptos básicos de la programación en R y Python en paralelo con ejemplos. Específicamente,
los temas de este capítulo incluyen variable, tipo, función, flujo de control, estructuras de datos y
programación orientada a objetos. El capítulo está diseñado tanto para principiantes como para un
público intermedio.

Capítulo 2: Más sobre programación en R/Python: después del primer capítulo, los temas de
este capítulo incluyen depuración, vectorización, paralelismo, trabajo con C++ en R/Python y
programación funcional. Estos temas se eligen para ayudar a la audiencia a familiarizarse con
algunos temas intermedios/avanzados en R/Python

Capítulo 3: data.table y pandas: en los primeros dos capítulos, nos enfocamos en las técnicas
de programación de propósito general. En este capítulo, presentamos los conceptos básicos de la
ciencia de datos, es decir, la manipulación de datos. Para la audiencia con poca experiencia en
ciencia de datos, comenzamos con una breve introducción a SQL. La mayor parte de este capítulo
se centra en los dos paquetes data.frame ampliamente utilizados, es decir, data.table en R y pandas
en Python. Los ejemplos uno al lado del otro que usan los dos paquetes no solo permiten a la
audiencia aprender los usos básicos de estas herramientas, sino que también se pueden usar como
un manual de referencia rápida.
Capítulo 4: Variables aleatorias, distribuciones y regresión lineal: en este capítulo, nos
enfocamos en las estadísticas y la regresión lineal, que es la base de la ciencia de datos. Para seguir
mejor este capítulo, recomiendo cualquier curso de estadística de nivel introductorio como requisito
previo. Los temas de este capítulo incluyen métodos de muestreo de variables aleatorias, ajuste de
distribución, simulación de cópula/distribución conjunta, cálculo de intervalos de confianza y prueba
de hipótesis. En secciones posteriores, también hablaremos de modelos de regresión lineal desde
cero. Muchos libros de texto presentan las teorías detrás de las regresiones lineales, pero aun así no
ayudan mucho en la implementación. Veremos cómo se implementa la regresión lineal como un
ejemplo de juguete tanto en R como en Python con la ayuda del álgebra lineal. También mostraré
cómo se puede usar el modelo básico de regresión lineal para la regresión lineal penalizada L2, es
decir, la regresión de cresta.
Capítulo 5: Optimización en la práctica: la mayoría de los modelos de aprendizaje
automático se basan en algoritmos de optimización. En este capítulo, damos una breve
introducción a la optimización. En concreto, hablaremos de convexidad, gradiente descendente,
herramientas de optimización de propósito general en R y Python, programación lineal y
algoritmos metaheurísticos, etc. En base a estas técnicas, veremos ejemplos de codificación sobre
estimación de máxima verosimilitud, regresión lineal, logística. Regresión, construcción de cartera,
problema del viajante de comercio.
Capítulo 6: Aprendizaje automático: una introducción suave: el aprendizaje automático es
un tema muy amplio. En este capítulo, trato de dar una introducción muy breve y suave al
aprendizaje automático. Comienza con una breve introducción del aprendizaje supervisado, el
aprendizaje no supervisado y el aprendizaje por refuerzo, respectivamente. Para el aprendizaje
supervisado, veremos la regresión potenciadora de gradientes con una implementación pura de
Python desde cero, a partir de la cual la audiencia podría aprender la traducción de los modelos
matemáticos a programas orientados a objetos. Para el aprendizaje no supervisado, se analizan el
modelo de mezcla gaussiana finita y PCA. Y para el aprendizaje por refuerzo, también usaremos un
juego simple como ejemplo para mostrar el uso de redes Q profundas. El objetivo de este capítulo es
doble. Primero, me gustaría darle a la audiencia una idea de cómo es el aprendizaje automático. En
segundo lugar, leer los fragmentos de código de este capítulo podría ayudar a la audiencia a repasar
o recapitular los temas de los capítulos anteriores}
Introducción a R/Python
Programación
En este capítulo, daré una introducción a la programación general en R y Python de

forma paralela.

1.1 CALCULADORA
R y Python son lenguajes de programación de propósito general que se pueden usar para escribir
software en una variedad de dominios. Pero por ahora, comencemos usándolos como calculadoras
básicas. Lo primero es tenerlos instalados. R1 y Python2 se pueden descargar desde su sitio web
oficial. En este libro, usaré R 3.5 y Python 3.7.
Para usar R/Python como calculadoras básicas, familiaricémonos con el modo interactivo.
Después de la instalación, podemos escribir R o Python (no distingue entre mayúsculas y
minúsculas, por lo que también podemos escribir r/python) para invocar el modo interactivo. Dado
que Python 2 está instalado de forma predeterminada en muchas máquinas, para evitar invocar a
Python 2, escribimos python3.7 en su lugar.
R
1
2 ~ $R
3
4 R version 3.5.1 (2018−07−02) −− "Feather Sp\footnoteray"
5 Copyright (C) 2018 The R Foundation for Statistical Computing
6 Platform: x86_64−apple−darwin15.6.0 (64−bit)
7
8 R is free software and comes with ABSOLUTELY NO WARRANTY.
9 You are welcome to redistribute it under certain conditions.
10 Type ’license()’ or ’licence()’ for distribution details.
11

1https://www.rproject.org
2https://www.python.org
12 Natural language support but running in an English locale
13
14 R is a collaborative project with many contributors.
15 Type ’contributors ()’ for more information and
16 ’citation ()’ on how to cite R or R packages in publications.
17
18 Type ’demo ()’ for some demos, ’help ()’ for on−line help, or
19 ’help.start()’ for an HTML browser interface to help.
20 Type ’q ()’ to quit R.
21
22 >

Python
1 ~ $python3.7
2 Python 3.7.1 (default, Nov 6 2018, 18:45:35)
3 [Clang 10.0.0 (clang−1000.11.45.5)] on Darwin
4 Type "help", "copyright", "credits" or "license" for more information.
5 >>>
Los mensajes que se muestran al invocar el modo interactivo dependen tanto de la versión de
R/Python instalada como de la máquina. Por lo tanto, puede ver diferentes mensajes en su
máquina local. Como decían los mensajes, para salir de R podemos teclear q(). Hay 3 opciones
solicitadas al preguntarle al usuario si el espacio de trabajo debe guardarse o no. Como solo
queremos usar R como una calculadora básica, salimos sin guardar espacio de trabajo.

Para salir de Python, simplemente podemos escribir exit().


R
1 > q()
2 Save workspace image? [y/n/c]: n
3~$

Una vez que estemos dentro del modo interactivo, podemos usar R/Python como calculadora.

R
1 > 1+1
2 [1] 2
3 > 2∗3+5
4 [1] 11
5 > log (2)
6 [1] 0.6931472
7 > exp(0)
8 [1] 1

Python
1 >>> 1+1
22
3 >>> 2∗3+5
4 11
5 >>> log(2)
6 Traceback (most recent call last):
7 File "", line 1, in
8 NameError: name ’log’ is not defined
9 >>> exp(0)
10 Traceback (most recent call last):
11 File "", line 1, in
12 NameError: name ’exp’ is not defined

Del fragmento de código anterior, R funciona perfectamente como una calculadora. Sin embargo,
surgen errores cuando llamamos a log(2) y exp(2) en Python. Los mensajes de error se explican por
sí mismos: la función de registro y la función exp no existen en el entorno Python actual. De hecho,
la función log y la función exp están definidas en el módulo matemático de Python. Un module3 es
un archivo que consta de código Python. Cuando invocamos el modo interactivo de Python, algunos
módulos integrados se cargan en el entorno actual de forma predeterminada. Pero el módulo
matemático no está incluido en estos módulos integrados. Eso explica por qué obtuvimos el
NameError cuando intentamos usar las funciones definidas en el módulo matemático . Para resolver
el problema, primero debemos cargar las funciones que se utilizarán utilizando la declaración de
importación de la siguiente manera.

Python
1 >>> from math import log, exp
2 >>> log(2)
3 0.6931471805599453
4 >>> exp(0)
5

1.2 VARIABLE Y TIPO


En la sección anterior hemos visto cómo usar R/Python como calculadoras. Ahora, veamos cómo
escribir programas reales. Primero, definamos algunas variables.
3https://docs.python.org/3/tutorial/modules.html
Aquí, definimos 4 variables diferentes a, b, x, e. Para obtener el tipo de cada
variable, podemos utilizar la función typeof() en R y type() en Python, respectivamente.

El tipo de x en R se llama carácter, y en Python se llama str.


1.3 FUNCIONES
Hemos visto dos funciones log y exp cuando usamos R/Python como calculadoras.
Una función es un bloque de código que realiza una tarea específica. Uno de los propósitos
principales de envolver un bloque de código en una función es reutilizar el código.
Es simple definir funciones en R/Python.

Aquí, definimos una función fun1 en R/Python. Esta función toma x como entrada y devuelve el cuadrado de
x. Cuando llamamos a una función, simplemente escribimos el nombre de la función
seguido del argumento de entrada dentro de un par de paréntesis. Vale la pena señalar que la
entrada o la salida no son necesarias para definir una función. Por ejemplo, podemos definir
una función fun2 para imprimir Hello World! sin entrada y salida.

Una diferencia importante entre los códigos R y Python es que los códigos Python están
estructurados con sangría. Cada línea lógica de código R/Python pertenece a un determinado
grupo. En R, usamos {} para determinar la agrupación de sentencias. Sin embargo, en Python
usamos espacios en blanco iniciales (espacios y tabulaciones) al comienzo de una línea lógica
para calcular el nivel de sangría de la línea, que se usa para determinar la agrupación de las
declaraciones. Veamos qué sucede si eliminamos el espacio en blanco inicial en la función de
Python anterior.

Python
1 >>> def fun1(x):
2 ... return x∗x # note the indentation
3 File "", line 2
4 return x∗x # note the indentation
5^
6 IndentationError: expected an indented block

Obtuvimos un error de sangría debido a la falta de sangría.

Volvamos a fun1 y echemos un vistazo más de cerca a la devolución. En Python, si queremos


devolver algo, tenemos que usar explícitamente la palabra clave return . return en R es una función
pero no es una función en Python y es por eso que no hay paréntesis después de return en Python.
En R, no se requiere devolución aunque necesitamos devolver algo de la función. En su lugar,
podemos poner las variables a devolver en la última línea de la función definida en R. Dicho esto,
podemos definir fun1 de la siguiente manera.

R
1 > fun1=function(x){x∗x}

A veces queremos dar un valor predeterminado a un argumento para una función, y tanto R
como Python permiten que las funciones tengan valores predeterminados.
En Python tenemos que poner los argumentos con valores por defecto al final, lo cual no se
requiere en R. Sin embargo, desde una perspectiva de legibilidad, siempre es mejor ponerlos al final. Es
posible que haya notado el mensaje de error anterior sobre la posición argumentada. En Python hay dos
tipos de argumentos, es decir, argumentos posicionales y argumentos de palabras clave. Simplemente
hablando, un argumento de palabra clave debe ir precedido por un identificador, por ejemplo, base en
el ejemplo anterior. Y los argumentos posicionales se refieren a argumentos que no son palabras clave.

1.4 FLUJOS DE CONTROL


Para implementar una lógica compleja en R/Python, es posible que necesitemos flujos de control.
1.4.1 Si/si no
Definamos una función para devolver el valor absoluto de entrada.
El fragmento de código anterior muestra cómo usar if/else en R/Python.
La sutil diferencia entre R y Python es que la condición posterior a if debe ser adoptada por paréntesis
en R pero es opcional en Python.
También podemos poner if después de else. Pero en Python, usamos elif como atajo.

1.4.2 Bucle For


Similar al uso de if en R, también tenemos que usar paréntesis después de la palabra clave for en
R.Pero en Python no debería haber paréntesis después de for.

Hay algo más interesante que el propio bucle for en los fragmentos anteriores.
En el código R, la expresión 1:3 crea un vector con los elementos 1, 2 y 3. En el código Python,
usamos la función range() por primera vez. Echemos un vistazo a ellos.
La función range () devuelve un objeto de tipo de rango, que representa una inmutable secuencia de
números. La función range() puede tomar tres argumentos, es decir, range(start, stop, step). Sin
embargo, start y step son opcionales. Es fundamental tener en cuenta que el argumento de parada que
define el límite superior de la secuencia es exclusivo. Y es por eso que para pasar del 1 al 3 tenemos
que pasar 4 como argumento de parada a la función range() . El argumento de paso especifica cuánto
aumentar de un número al siguiente. Los valores predeterminados de inicio y paso son 0 y 1,
respectivamente.
1.4.3 Bucle mientras

Habrás notado que en Python podemos hacer i+=1 para sumar 1 a i, lo cual no es factible en R por
defecto. Tanto el bucle for como el bucle while se pueden anidar.
1.4.4 Interrumpir/continuar
Break/continue ayuda si queremos romper el ciclo for/while antes, o para omitir una iteración
específica. En R, la palabra clave para continuar se llama siguiente, en contraste con continuar en
Python. La diferencia entre romper y continuar es que llamar a romper saldría del bucle más interno
(cuando hay bucles anidados, solo se ve afectado el bucle más interno); mientras que llamar a continuar
simplemente omitiría la iteración actual y continuaría el ciclo si no hubiera terminado.
1.5 ALGUNAS ESTRUCTURAS DE DATOS INTEGRADAS
En las secciones anteriores, no hemos visto mucha diferencia entre R y Python. 1 (numérico) a "1"
(carácter) ya que los elementos deben tener el mismo tipo. ¿Qué sucede si el índice de acceso está fuera
de los límites? Sin embargo, con respecto a las estructuras de datos integradas, existen algunas
diferencias significativas que veremos en esta sección.
1.5.1 Vector en R y lista en Python
En R, podemos usar la función c() para crear un vector; Un vector es una secuencia de elementos del
mismo tipo. En Python, podemos usar [] para crear una lista, que también es una secuencia de
elementos. Pero los elementos de una lista no necesitan tener el mismo tipo. Para obtener el número de
elementos en un vector en R, usamos la función length(); y para obtener el número de elementos en una
lista en Python, usamos la función len().

En el fragmento de código anterior, el primer elemento de la variable z en R se fuerza desde 1


(numérico) a "1" (carácter) ya que los elementos deben tener el mismo tipo.
Para acceder a un elemento específico de un vector o lista, podríamos usar []. En R, los tipos de
secuencia se indexan comenzando con un subíndice. Por el contrario, los tipos de secuencia en Python
se indexan comenzando con el subíndice cero.
¿Qué sucede si el índice de acceso está fuera de los límites?

En Python, el número de índice negativo significa indexar desde el final de la lista. Por lo tanto, x[−1]
apunta al último elemento y x[−2] apunta al penúltimo elemento de la lista. Pero R no admite la
indexación con números negativos de la misma manera que Python. Específicamente, en R x[−index]
devuelve un nuevo vector con x[index] excluido.
Cuando intentamos acceder con un índice fuera de los límites, Python lanzaría un IndexError. El
comportamiento de R cuando se indexa fuera de los límites es más interesante. Primero, cuando
tratamos de acceder a x[0] en R, obtenemos un numeric(0) cuya longitud también es 0. Dado que su
longitud es 0, numeric(0) puede interpretarse como un vector numérico vacío. Cuando intentamos
acceder a x[longitud(x)+1] obtenemos un NA. En R, también hay NaN y NULL
NaN significa "No es un número" y se puede verificar comprobando su tipo: "doble". 0/0 daría como
resultado un NaN en R. NA en R generalmente representa valores faltantes. Y NULL representa un
objeto NULL (vacío). Para verificar si un valor es NA, NaN o NULL, podemos usar is.na(), is.nan() o
is.null, respectivamente.
En Python, no hay NA o NaN integrados. La contraparte de NULL en Python es None. En Python,
podemos usar la palabra clave is o == para verificar si un valor es igual a Ninguno.
Del fragmento de código anterior, también notamos que en R el valor de tipo booleano se escribe como
"VERDADERO/FALSO", en comparación con "Verdadero/Falso" en Python. Aunque en R
"VERDADERO/FALSO" también se puede abreviar como "V/F", no recomiendo usar la abreviatura
Hay un hecho interesante de que no podemos agregar un NULL a un vector en R, pero es factible
agregar un None a una lista en Python.

A veces queremos crear un vector/lista con elementos replicados, por ejemplo, un vector/lista con todos
los elementos iguales a 0.

Cuando usamos el operador para hacer réplicas de una lista, hay una advertencia: si el elemento
dentro de la lista es mutable, los elementos replicados apuntan a la misma dirección de memoria. Como
consecuencia, si un elemento muta, otros elementos también se ven afectados.
Python
1 >>> x=[0] # x is a list which is mutable
2 >>> y=[x]∗5 # each element in y points to x
3 >>> y
4 [[0], [0], [0], [0], [0]]
5 >>> y[2]=2 # we point y[2] to 2 but x is not mutated
6 >>> y
7 [[0], [0], 2, [0], [0]]
8 >>> y[1][0]=−1 # we mutate x by changing y[1][0] from 0 to −1
9 >>> y
10 [[−1], [−1], 2, [−1], [−1]]
11 >>> x
12 [−1]

¿Cómo obtener una lista con elementos replicados pero apuntando a diferentes direcciones de
memoria?

Python
1 >>> x=[0]
2 >>> y=[x[:] for _ in range(5)] # [:] makes a copy of the list x; another solution is [list(x) for _ in range(5)]
3 >>> y
4 [[0], [0], [0], [0], [0]]
5 >>> y[0][0]=2
6 >>> y
7 [[2], [0], [0], [0], [0]]

Además de acceder a un elemento específico de un vector/lista, es posible que también necesitemos realizar
cortes, es decir, seleccionar un subconjunto del vector/lista. Hay dos enfoques básicos de corte:
• Basado en números enteros
R
1 > x=c(1,2,3,4,5,6)
2 > x[2:4]
3 [1] 234
4 > x[c(1,2,5)] # a vector of indices
5 [1] 125
6 > x[seq(1,5,2)] # seq creates a vector to be used as indices
7 [1] 135
Python
1 >>> x=[1,2,3,4,5,6]
2 >>> x[1:4] # x[start:end] start is inclusive but end is exclusive
3 [2, 3, 4]
4 >>> x[0:5:2] # x[start:end:step]
5 [1, 3, 5]
El fragmento de código anterior usa el carácter hash # para los comentarios tanto en R como en Python.
Todo lo que esté después de # en la misma línea será tratado como comentario (no ejecutable). En el
código R, también usamos la función seq() para crear un vector.
Cuando veo una función que no he visto antes, podría buscarla en Google o usar el mecanismo auxiliar
integrado. Específicamente, en uso de R ? y en Python usa ayuda(
R

1 > ?seq
Python
>>> help(print)

• Basado en condiciones
El corte basado en condiciones significa seleccionar un subconjunto de los elementos que satisfacen
ciertas condiciones. En R, es bastante sencillo mediante el uso de un vector booleano cuya longitud es
la misma que la del vector que se va a dividir.
R
1 > x=c(1,2,5,5,6,6)
2 > x[x %% 2==1] # %% is the modulo operator in R; we select the odd elements
3 [1] 155
4 > x %% 2==1 # results in a boolean vector with the same length as x
5 [1] TRUE FALSE TRUE TRUE FALSE FALSE

El corte basado en condiciones en Python es bastante diferente al de R. El requisito previo es la


comprensión de listas, lo que proporciona una forma concisa de crear nuevas listas en Python. Por
ejemplo, creemos una lista de cuadrados de otra lista.

Python

1 >>> x=[1,2,5,5,6,6]

2 >>> [e∗∗2 for e in x] # ∗∗ is the exponent operator, i.e., x∗∗y means x to the power of y

3 [1, 4, 25, 25, 36, 36]

También podemos usar la declaración if con comprensión de lista para filtrar una lista y lograr el corte
de lista.
Python

1 >>> x=[1,2,5,5,6,6]

2 >>> [e for e in x if e%2==1] # % is the modulo operator in Python

3 [1, 5, 5]

También es común usar if/else con comprensión de listas para lograr operaciones más complejas. Por
ejemplo, dada una lista x, vamos a crear una nueva lista y para que los elementos no negativos en x
sean elevados al cuadrado y los elementos negativos sean reemplazados por 0

Python

1 >>> x=[1,−1,0,2,5,−3]

2 >>> [e∗∗2 if e>=0 else 0 for e in x]

3 [1, 0, 0, 4, 25, 0]

El ejemplo anterior muestra el poder de la comprensión de listas. Para usar if con comprensión de lista,
la instrucción if debe colocarse al final después de la instrucción de bucle for ; pero para usar if/else
con comprensión de listas, la instrucción if/else debe colocarse antes de la instrucción de bucle for

También podemos modificar el valor de un elemento en una variable vector/lista

Dos o múltiples vectores/listas se pueden concatenar fácilmente

Como la estructura de la lista en Python es mutable, hay muchas cosas que podemos hacer con la lista
Python

1 >>> x=[1,2,3]
2 >>> x.append(4) # append a single value to the list x
3 >>> x
4 [1, 2, 3, 4]
5 >>> y=[5,6]
6 >>> x.extend(y) # extend list y to x
7 >>> x
8 [1, 2, 3, 4, 5, 6]
9 >>> last=x.pop() # pop the last element from x
10 >>> last
11 6
12 >>> x
13 [1, 2, 3, 4, 5]
Me gusta la estructura de lista en Python mucho más que la estructura de vector en R. list en Python
tiene muchas más características útiles que se pueden encontrar en la documentación oficial de
python4.
1.5.2 Matriz
Array es una de las estructuras de datos más importantes en la programación científica. En R, también
hay un tipo de objeto "matriz", pero según mi propia experiencia, casi podemos ignorar su existencia y
usar una matriz en su lugar. Definitivamente podemos usar la lista como matriz en Python, pero
muchas operaciones de álgebra lineal no son compatibles con el tipo de lista.
Afortunadamente, hay un paquete de Python listo para usar.

R
1 > x=1:12
2 > array1=array(x, c(4,3)) # convert vector x to a 4 rows ∗ 3 cols array
3 > array1
4 [,1] [,2] [,3]
5 [1,] 1 5 9
6 [2,] 2 6 10
7 [3,] 3 7 11
8 [4,] 4 8 12
9 > y=1:6
10 > array2=array(y, c(3,2)) # convert vector y to a 3 rows ∗ 2 cols array
11 > array2
12 [,1] [,2]
13 [1,] 1 4
14 [2,] 2 5
15 [3,] 3 6
16 > array3 = array1 %∗% array2 # %∗% is the matrix multiplication operator
17 > array3
18 [,1] [,2]
19 [1,] 38 83
20 [2,] 44 98
21 [3,] 50 113
22 [4,] 56 128
23 > dim(array3) # get the dimension of array3
24 [1] 4 2

Python
1 >>> import numpy as np # we import the numpy module and alias it as np
2 >>> array1=np.reshape(list(range(1,13)),(4,3)) # convert a list to a 2d np.array
3 >>> array1
4 array([[ 1, 2, 3],
5 [ 4, 5, 6],
6 [ 7, 8, 9],
7 [10, 11, 12]])
8 >>> type(array1)
9 <class ’numpy.ndarray’>
10 >>> array2=np.reshape(list(range(1,7)),(3,2))
11 >>> array2
12 array([[1, 2],
13 [3, 4],
14 [5, 6]])
15 >>> array3=np.dot(array1, array2) # matrix multiplication using np.dot()
16 >>> array3
17 array([[ 22, 28],
18 [ 49, 64],
19 [ 76, 100],
20 [103, 136]])
21 >>> array3.shape # get the shape(dimension) of array3
22 (4, 2)

Es posible que haya notado que los resultados del fragmento de código R y el fragmento de código de
Python son diferentes. La razón es que en R la conversión de un vector a una matriz es por columna;
pero en numpy, la remodelación de una lista a un numpy.array 2D es por fila.
Hay dos formas de remodelar una lista a un numpy.array 2D por columna.
Python
1 >>> array1=np.reshape(list(range(1,13)),(4,3),order=’F’) # use order=’F’
2 >>> array1
3 array([[ 1, 5, 9],
4 [ 2, 6, 10],
5 [ 3, 7, 11],
6 [ 4, 8, 12]])
7 >>> array2=np.reshape(list(range(1,7)),(2,3)).T # use .T to transpose an array
8 >>> array2
9 array([[1, 4],
10 [2, 5],
11 [3, 6]])
12 >>> np.dot(array1, array2) # now we get the same result as using R
13 array([[ 38, 83],
14 [ 44, 98],
15 [ 50, 113],
16 [ 56, 128]])

Para obtener más información sobre numpy, el sitio web oficial5 tiene una excelente
documentación/tutorial también.
El término transmisión describe cómo se manejan las matrices con diferentes formas también durante
las operaciones aritméticas. A continuación se muestra un ejemplo sencillo de transmisión

Sin embargo, las reglas de transmisión en R y Python no son exactamente las mismas.
Desde el código R, vemos que la transmisión en R es como reciclar junto con la columna. En Python,
cuando las dos matrices tienen diferentes dimensiones, la que tiene menos dimensiones se rellena con
unos en su lado inicial. De acuerdo con esta regla, cuando hacemos x y, la dimensión de x es (3, 2) pero
la dimensión de y es 3. Por lo tanto, la dimensión de y se completa con (1, 3), lo que explica lo que
sucede cuando x y
5 1.5.3 Lista en R y diccionario en Py
Sí, en R también hay un tipo de objeto llamado lista. La principal diferencia entre un vector y una lista
en R es que una lista puede contener diferentes tipos de elementos. list en R admite el acceso basado en
enteros usando [[]] (en comparación con [] para vector)
R
1 > x=list(1,’hello world!’)
2>x
3 [[1]]
4 [1] 1
5
6 [[2]]
7 [1] "hello world!"
8
9 > x[[1]]
10 [1] 1
11 > x[[2]]
12 [1] "hello world!"
13 > length(x)
14 [1] 2
La lista en R podría tener un nombre y admitir el acceso por nombre a través de [[]] o $ operador. Pero
el vector en R también se puede nombrar y admitir el acceso por nombre.
R
1 > x=c(’a’=1,’b’=2)
2 > names(x)
3 [1] "a" "b"
4 > x[’b’]
5b
62
7 > l=list(’a’=1,’b’=2)
8 > l[[’b’]]
9 [1] 2
10 > l$b
11 [1] 2
12 > names(l)
13 [1] "a" "b"
Sin embargo, los elementos de la lista en Python no se pueden nombrar como R. Si necesitamos la
función de acceder por nombre en Python, podemos usar la estructura del diccionario. Si usó Java
antes, puede considerar el diccionario en Python como la contraparte de HashMap en Java.
Esencialmente, un diccionario en Python es una colección de pares clave:valor.

Python
1 >>> x={’a’:1,’b’:2} # {key:value} pairs
2 >>> x
3 {’a’: 1, ’b’: 2}
4 >>> x[’a’]
51
6 >>> x[’b’]
72
8 >>> len(x) # number of key:value pairs
92
10 >>> x.pop(’a’) # remove the key ’a’ and we get its value 1
11 1
12 >>> x
13 {’b’: 2}

A diferencia del diccionario en Python, la lista en R no admite la operación pop() . De este modo, para
modificar una lista en R, se debe crear una nueva explícita o implícitamente.
1.5.4 data.frame, data.table y pandas

Data.frame es un tipo incorporado en R para la manipulación de datos. En Python, no existe tal


estructura de datos incorporada ya que Python es un lenguaje de programación de propósito más
general. La solución para data.frame en Python es el módulo pandas6 .
Antes de sumergirnos en data.frame, es posible que sienta curiosidad por saber por qué lo necesitamos.
En otras palabras, ¿por qué no podemos simplemente usar vectores, listas, arreglos/matrices y
diccionarios para todas las tareas de manipulación de datos? Diría que sí: data.frame no es una
característica imprescindible para la mayoría de las operaciones ETL (extracción, transformación y
carga). Pero data.frame nos proporciona una forma muy intuitiva de entender el conjunto de datos
estructurados. Un marco de datos suele ser plano con 2 dimensiones, es decir, fila y columna. La
dimensión de la fila es a través de múltiples observaciones y la dimensión de la columna es a través de
múltiples atributos/ características. Si está familiarizado con la base de datos relacional, un marco de
datos se puede ver como una tabla.
Veamos un ejemplo del uso de data.frame para representar la información de los empleados en una
compañía

Hay bastantes formas de crear data.frame. El más utilizado es crear un objeto data.frame a partir de
una matriz/matriz. También es posible que necesitemos convertir un marco de datos numérico en una
matriz/matriz.
R
1 > x=array(rnorm(12), c(3,4))
2>x
3 [,1] [,2] [,3] [,4]
4 [1,] −0.8101246 −0.8594136 −2.260810 0.5727590
5 [2,] −0.9175476 0.1345982 1.067628 −0.7643533
6 [3,] 0.7865971 −1.9046711 −0.154928 −0.6807527
7 > random_df=as.data.frame(x)
8 > random_df
9 V1 V2 V3 V4
10 1 −0.8101246 −0.8594136 −2.260810 0.5727590
11 2 −0.9175476 0.1345982 1.067628 −0.7643533
12 3 0.7865971 −1.9046711 −0.154928 −0.6807527
13 > data.matrix(random_df)
14 V1 V2 V3 V4
15 [1,] −0.8101246 −0.8594136 −2.260810 0.5727590
16 [2,] −0.9175476 0.1345982 1.067628 −0.7643533
17 [3,] 0.7865971 −1.9046711 −0.154928 −0.6807527

Python
1 >>> import numpy as np
2 >>> import pandas as pd
3 >>> x=np.random.normal(size=(3,4))
4 >>> x
5 array([[−0.54164878, −0.14285267, −0.39835535, −0.81522719],
6 [ 0.01540508, 0.63556266, 0.16800583, 0.17594448],
7 [−1.21598262, 0.52860817, −0.61757696, 0.18445057]])
8 >>> random_df = pd.DataFrame(x)
9 >>> random_df
10 0 1 2 3
11 0 −0.541649 −0.142853 −0.398355 −0.815227
12 1 0.015405 0.635563 0.168006 0.175944
13 2 −1.215983 0.528608 −0.617577 0.184451
14 >>> np.asarray(random_df)
15 array([[−0.54164878, −0.14285267, −0.39835535, −0.81522719],
16 [ 0.01540508, 0.63556266, 0.16800583, 0.17594448],
17 [−1.21598262, 0.52860817, −0.61757696, 0.18445057]])

En general, las operaciones en un arreglo/matriz son mucho más rápidas que aquellas en un marco de
datos. En R, podemos usar la función integrada data.matrix para convertir un data.frame en una
matriz/matriz. En Python, podríamos usar la función asarray en el módulo numpy.
Aunque data.frame es un tipo incorporado, no es muy eficiente para muchas operaciones.

Sugeriría usar data.table7 siempre que sea posible. dplyr también es un paquete muy popular en R para
la manipulación de datos. Muchos buenos recursos están disponibles en línea para aprender data.table y
pandas.

1.6 REVISITA DE VARIABLES


Hemos hablado de variables y funciones hasta ahora. Cuando una función tiene un nombre, su nombre
también es una variable válida. Después de todo, ¿qué es una variable?
En matemáticas, una variable es un símbolo que representa un elemento, y lo hacemos
No importa si conceptualizamos una variable en nuestra mente, o la escribimos en un papel. Sin
embargo, en programación una variable no es solo un símbolo. Tenemos que entender que una variable
es un nombre dado a una ubicación de memoria en los sistemas informáticos. Cuando ejecutamos x=2
en R o Python, en algún lugar de la memoria tiene el valor 2 y la variable (nombre) apunta a esta
dirección de memoria. Si ejecutamos más y = x, la variable y apunta a la misma ubicación de memoria
señalada por (x). ¿Qué pasa si ejecutamos x=3? No modifica la memoria que almacena el valor 2. En
cambio, en algún lugar de la memoria ahora tiene el valor 3 y esta ubicación de memoria tiene un
nombre x. Y la variable y no se ve afectada en absoluto, así como la ubicación de memoria a la que
apunta.
1.6.1 Mutabilidad
Casi todo en R o Python es un objeto, incluidas estas estructuras de datos que presentamos en secciones
anteriores. La mutabilidad es una propiedad de los objetos, no de las variables, porque una variable es
solo un nombre.
Una lista en Python es mutable, lo que significa que podemos cambiar los elementos almacenados en el
objeto de lista sin copiar el objeto de lista de una ubicación de memoria a otra.
Podemos usar la función id en Python para verificar la ubicación de la memoria de una variable. En el
siguiente código, modificamos el primer elemento del objeto de lista con el nombre x. Y dado que la
lista de Python es mutable, la dirección de memoria de la lista no cambia.

Python
1 >>> x=list(range(1,1001)) # list() convert a range object to a list
2 >>> hex(id(x)) # print the memory address of x
3 ’0x10592d908’
4 >>> x[0]=1.0 # from integer to float
5 >>> hex(id(x)) 6 ’0x10592d908’
¿Hay alguna estructura de datos inmutable en Python? Sí, por ejemplo, la tupla es inmutable, que
contiene una secuencia de elementos. El acceso a elementos y el subconjunto de tupla sigue las mismas
reglas que la lista en Python.
Python
1 >>> x=(1,2,3,) # use () to create a tuple in Python, it is better to always put a comma in the end
2 >>> type(x)
3 < class ’tuple’>
4 >>> len(x)
53
6 >>> x[0]
71
8 >>> x[0]=−1¿
9 Traceback (most recent call last):
10 File "", line 1, in
11 TypeError: ’tuple’ object does not support item assignment

Si tenemos dos variables de Python apuntando a la misma memoria, cuando modificamos la memoria a través de
una variable, la otra también se ve afectada como esperábamos (vea el ejemplo a continuación).

Python
1 >>> x=[1,2,3]
2 >>> id(x)
3 4535423616
4 >>> x[0]=0
5 >>> x=[1,2,3]
6 >>> y=x
7 >>> id(x)
8 4535459104
9 >>> id(y)
10 4535459104
11 >>> x[0]=0
12 >>> id(x)
13 4535459104
14 >>> id(y)
15 4535459104
16 >>> x
17 [0, 2, 3]
18 >>> y
19 [0, 2, 3]

Por el contrario, la mutabilidad del vector en R es más compleja ya veces confusa. Primero, veamos el
comportamiento cuando se da un solo nombre al objeto vector almacenado en la memoria.

R
1 > a=c(1,2,3)
2 > .Internal(inspect(a))
3 @7fe94408f3c8 14 REALSXP g0c3 [NAM(1)] (len=3, tl=0) 1,2,3
4 > a[1]=0
5 > .Internal(inspect(a))
6 @7fe94408f3c8 14 REALSXP g0c3 [NAM(1)] (len=3, tl=0) 0,2,3

Está claro en este caso que el objeto vector es mutable ya que la dirección de memoria no cambia
después de la modificación. ¿Qué pasa si hay un nombre adicional dado a la memoria?
R
1 > a=c(1,2,3)
2 > b=a
3 > .Internal(inspect(a))
4 @7fe94408f238 14 REALSXP g0c3 [NAM(2)] (len=3, tl=0) 1,2,3
5 > .Internal(inspect(b))
6 @7fe94408f238 14 REALSXP g0c3 [NAM(2)] (len=3, tl=0) 1,2,3
7 > a[1]=0
8 > .Internal(inspect(a))
9 @7fe94408f0a8 14 REALSXP g0c3 [NAM(1)] (len=3, tl=0) 0,2,3
10 > .Internal(inspect(b))
11 @7fe94408f238 14 REALSXP g0c3 [NAM(2)] (len=3, tl=0) 1,2,3
12 > a
13 [1] 0 2 3
14 > b
15 [1] 1 2 3

Antes de la modificación, tanto la variable a como la b apuntan al mismo objeto vectorial en la


memoria. Pero sorprendentemente, después de la modificación, la dirección de memoria de la variable
a también cambió, lo que se denomina "copiar al modificar" en R. Y debido a este comportamiento
único, la modificación de a no afecta el objeto almacenado en la memoria anterior y, por lo tanto, el
objeto vector es inmutable en este caso. La mutabilidad de la lista R es similar a la del vector R.
1.6.2 Variable como argumento de función
La mayoría de las funciones/métodos en R y Python toman algunas variables como argumento. ¿Qué
sucede cuando pasamos las variables a una función? no cambia después de la modificación. ¿Qué pasa
si hay un nombre adicional dado a la memoria? En Python, la variable, es decir, el nombre del objeto,
se pasa a una función. Si la variable apunta a un objeto inmutable, cualquier modificación a la variable,
es decir, el nombre, no persiste. Sin embargo, cuando la variable apunta a un objeto mutable, la
modificación del objeto almacenado en memoria persiste. Veamos los ejemplos a continuación.
Vemos que el objeto se pasa a función por su nombre. Si el objeto es inmutable, se crea una nueva
copia en la memoria cuando se realiza alguna modificación en el objeto original. Cuando el objeto es
inmutable, no se realiza ninguna copia nueva y, por lo tanto, el cambio persiste fuera de la función.
En R, el objeto pasado siempre se copia en una modificación dentro de la función, La gente puede
argumentar que las funciones de R no son tan flexibles como las funciones de Python. Sin embargo,
tiene más sentido hacer programación funcional en R ya que normalmente no podemos modificar los
objetos pasados a una función. ¿Cuál es el alcance de una variable y por qué es importante? Primero
echemos un vistazo a los fragmentos de código a continuación,y por lo tanto no se puede hacer ninguna
modificación al objeto original en la memoria.
R
1 > f=function(x){
2 + print(.Internal(inspect(x)))
3 + x[1]=x[1] 1
4 − + print(.Internal(inspect(x)))
5 + print(x)
6+}
7>
8 > a=c(1,2,3)
9 > .Internal(inspect(a))
10 @7fe945538688 14 REALSXP g0c3 [NAM(1)] (len=3, tl=0) 1,2,3
11 > f(a)
12 @7fe945538688 14 REALSXP g0c3 [NAM(3)] (len=3, tl=0) 1,2,3
13 [1] 1 2 314 @7fe945538598
14 REALSXP g0c3 [NAM(1)] (len=3, tl=0) 0,2,3
15 [1] 0 2 3
16 [1] 0 2 3
17 > a
18 [1] 1 2 3
La gente puede argumentar que las funciones de R no son tan flexibles como las funciones de Python.
Sin embargo, tiene más sentido hacer programación funcional en R ya que normalmente no podemos
modificar los objetos pasados a una función.
1.6.3 Alcance de las variables
¿Cuál es el alcance de una variable y por qué es importante? Primero echemos un vistazo a los
fragmentos de código a continuación.
Los resultados del código anterior parecen extraños antes de conocer el concepto de alcance
variable. Dentro de una función, una variable puede hacer referencia a un argumento/ parámetro de
función o podría declararse formalmente dentro de la función, que se denomina variable local. Pero en
el código anterior, x no es ni un argumento de función ni una variable local. ¿ Cómo sabe la función
print() a qué apunta el identificador x ?
El alcance de una variable determina dónde está disponible/accesible la variable (se puede hacer
referencia a ella). Tanto R como Python aplican el alcance léxico/estático para las variables, que
establecen el alcance de una variable en función de la estructura del programa. En el alcance estático,
cuando se hace referencia a una variable 'desconocida', la función intentará encontrarla desde el bloque
más cercano. Eso explica cómo la función print() podría encontrar la variable x.
En el código R anterior, x=x+1, la primera x es una variable local creada por el operador = ; se hace
referencia a la segunda x dentro de la función, por lo que se aplica la regla de alcance estático. Como
resultado, se crea una variable local x que es igual a 2, que es independiente con la x fuera de la
función var_func_2(). Sin embargo, en Python, cuando a una variable se le asigna un valor en una
declaración, la variable se trataría como una variable local y eso explica el UnboundLocalError.
¿Es posible cambiar una variable dentro de una función que se declara fuera de la función sin
pasarla como argumento? Basado únicamente en la regla de alcance estático, es imposible. Pero hay
soluciones alternativas tanto en R/Python. En R, necesitamos la ayuda del entorno; y en Python
podemos usar la palabra clave global.
Entonces, ¿qué es un entorno en R? Un entorno es un lugar donde se almacenan objetos. Cuando
invocamos la sesión R interactiva, se crea automáticamente un entorno llamado .GlobalEnv . También
podemos usar la función environment() para obtener el entorno actual. La función ls() puede tomar un
entorno como argumento para listar todos los objetos dentro del entorno.
R
1 $r
2 > typeof(.GlobalEnv)
3 [1] "environment"
4 > environment()
5 < environment: R_GlobalEnv>
6 > x=1
7 > ls(environment())
8 [1] "x"
9 > env_func_1=function(x){
10 + y=x+1
11 + print(environment())
12 + ls(environment())
13 + }
14 > env_func_1(2)
15 < environment: 0x7fc59d165a20>
16 [1] "x" "y"
17 > env_func_2=function(){print(environment())}
18 > env_func_2()
19 < environment: 0x7fc59d16f520>
El código anterior muestra que cada función tiene su propio entorno que contiene todos los argumentos
de la función y las variables locales declaradas dentro de la función. Para cambiar una variable
declarada fuera de una función, necesitamos el acceso del entorno que encierra la variable a cambiar.
Hay una función parent_env(e) que devuelve el entorno principal del entorno dado e en R. Con esta
función, podemos cambiar el valor de x declarado en .GlobalEnv dentro de una función que también
está declarada en .GlobalEnv. La palabra clave global en Python funciona de una manera totalmente
diferente, que es simple pero menos flexible.
Rara vez uso la palabra clave global en Python, si es que alguna vez lo hago. Pero el entorno en R
podría ser muy útil en algunas ocasiones. En R, el entorno podría usarse como una versión puramente
mutable de la estructura de datos de la lista. Usemos la función R tracemem para rastrear la copia de un
objeto. No vale nada que tracemem no pueda rastrear funciones R.

En realidad, el objeto de un tipo de clase R6 también es un entorno.


R
1 > # load the Complex class that we defined in chapter 1
2 > x = Complex$new(1,2)
3 > typeof(x)
4 [1] "environment"
En Python, podemos asignar valores a múltiples variables en una línea.

Aunque en el fragmento de la izquierda de arriba no hay paréntesis que abarquen 1, 2 después del
operador = , primero se crea una tupla y luego se desempaqueta y se asigna a x, y. Tal mecanismo no
existe en R, pero podemos definir nuestro propio operador de asignación múltiple con la ayuda del
entorno.
R capítulo1/multi_asignación.R
1 ‘%=%‘ = function(left, right) {
2 # we require the RHS to be a list strictly
3 stopifnot(is.list(right))
4 # dest_env is the desitination environment enclosing the variables on LHS
5 dest_env = parent.env(environment())
6 left = substitute(left)
7
8 recursive_assign = function(left, right, dest_env) {
9 if (length(left) == 1) {
10 assign(x = deparse(left),
11 value = right,
12 envir = dest_env)
13 return()
14 }
15 if (length(left) != length(right) + 1) {
16 stop("LHS and RHS must have the same shapes")
17 }
18
19 for (i in 2:length(left)) {
20 recursive_assign(left[[i]], right[[i − 1]], dest_env)
21 }
22 }
23
24 recursive_assign(left, right, dest_env)
25 }
Antes de profundizar más en el script, primero veamos el uso del operador de asignación múltiple que
definimos.
R
1 > source(’multi_assignment.R’)
2 > c(x,y,z) %=% list(1,"Hello World!",c(2,3))
3>x
4 [1] 1
5>y
6 [1] "Hello World!"
7>z
8 [1] 2 3
9 > list(a,b) %=% list(1,as.Date(’2019−01−01’))
10 > a
11 [1] 1
12 > b
13 [1] "2019−01−01"
En el operador %=% definido anteriormente, usamos dos funciones de sustitución, aparte de las cuales
son muy poderosas pero menos conocidas por los novatos de R. Para comprender mejor estas
funciones, así como algunas otras funciones de R menos conocidas, vale la pena leer el tutorial de
Rchaeology9.
También es interesante ver que definimos la función recursive_assign dentro de la función %=% .
Tanto R como Python admiten el concepto de funciones de primera clase. Construyamos una clase en
R/Python para representar números complejos. Más específicamente, una función en R/Python es un
objeto, que puede ser:
1. almacenado como una variable;
2. pasado como argumento de función;
3. devuelto de una función.
La idea esencial detrás de la función recursive_assign es una búsqueda en profundidad (DFS), que es
un algoritmo fundamental de recorrido de gráficos10. En el contexto de la función recursive_assign ,
usamos DFS para recorrer el árbol de análisis del argumento de la izquierda creado al llamar a
replace(left).
1.7 PROGRAMACIÓN ORIENTADA A OBJETOS (OOP) EN R/PYTHON
Todos los códigos que escribimos anteriormente siguen el paradigma de programación
procedimental11. También podemos hacer programación funcional (FP) y POO en R/Python. En esta
sección, centrémonos en OOP en R/Python.
La clase es el concepto clave en OOP. En R hay dos sistemas integrados de uso común para definir
clases, es decir, S3 y S4. Además, existe un paquete externo R612 que define las clases R6. S3 es un
sistema liviano pero su estilo es bastante diferente al de OOP en muchos otros lenguajes de
programación. El sistema S4 sigue los principios de la programación moderna orientada a objetos
mucho mejor que S3. Sin embargo, el uso de clases S4 es bastante tedioso. Ignoraría S3/S4 e
introduciría R6, que está más cerca de la clase en Python.
Construyamos una clase en R/Python para representar números complejos.
R
1 > library(R6) # load the R6 package
2>
3 > Complex = R6Class("Complex",
4 + public = list( # only elements declared in this list are accessible by the object of this class
5 + real = NULL ,
6 + imag = NULL ,
7 + # the initialize function would be called automatically when we create an object of the class
8 + initialize = function(real, imag){
9 + # call functions to change real and imag values
10 + self$set_real(real)
11 + self$set_imag(imag)
12 + },
13 + # define a function to change the real value
14 + set_real = function(real){
15 + self$real=real
16 + },
17 + # define a function to change the imag value
18 + set_imag = function(imag){
19 + self$imag = imag
20 + },
21 + # override print function
22 + print = function(){
23 + cat(paste0(as.character(self$real),’+’,as.character(self$ imag),’j’),’\n’)
24 + }
25 + )
26 + )
27 > # let’s create a complex number object based on the Complex class we defined above using the
new function
28 > x = Complex$new(1,2)
29 > x
30 1+2j
31 > x$real # the public attributes of x could be accessed by $ operator
32 [1] 1
Python
1 >>> class Complex:
2 ... # the __init__ function would be called automatically when we create an object of the class
3 ... def __init__(self, real, imag):
4 ... self.real = real
5 ... self.imag = imag
6
7 ... def __repr__(self):
8 ... return "{0}+{1}j".format(self.real, self.imag)
9 ...
10 >>> x = Complex(1,2)
11 >>> x
12 1+2j
13 >>> x.real # different from the $ operator in R, here we use . to access the attribute of an object
14 1
Al anular la función de impresión en la clase R6, podemos imprimir el objeto en el formato de
real+imag j. Para lograr el mismo efecto en Python, anulamos el método __repr__. En Python, nos
referimos a las funciones definidas en las clases como métodos.
Y anular un método significa cambiar la implementación de un método proporcionado por uno de sus
ancestros. Para comprender el concepto de ancestros en programación orientada a objetos, es necesario
comprender el concepto de herencia13.
Puede que sienta curiosidad por el doble guión bajo que rodea a los métodos, como __init__ y
__repr__. Estos métodos son conocidos como métodos especiales14. En la definición del método
especial __repr__ en el código de Python, se usa el método de formato de str object15
Los métodos especiales pueden ser muy útiles si los usamos en casos adecuados. Por ejemplo, podemos
usar el método especial __add__ para implementar el operador + para la clase Complex que definimos
anteriormente.
Python
1 >>> class Complex:
2 ... def __init__(self, real, imag):
3 ... self.real = real
4 ... self.imag = imag
5 ... def __repr__(self):
6 ... return "{0}+{1}j".format(self.real, self.imag)
7 ... def __add__(self, another):
8 ... return Complex(self.real + another.real, self.imag + another.imag)
9 ...
10 >>> x = Complex(1,2)
11 >>> y = Complex(2,4)
12 >>> x+y # + operator works now
13 3+6j
También podemos implementar el operador + para la clase Complex en R como lo hemos hecho para
Python.
R
1 > ‘+.Complex ‘ = function(x, y){
2 + Complex$new(x$real+y$real, x$imag+y$imag)
3+}
4 > x=Complex$new(1,2)
5 > y=Complex$new(2,4)
6 > x+y
7 3+6j
La parte más interesante del código anterior es '+.Complex'. Primero, ¿por qué usamos '' para citar el
nombre de la función? Antes de entrar en esta pregunta, echemos un vistazo a las reglas de
nomenclatura de variables de Python 316.
1 Dentro del rango ASCII (U+0001...U+007F), los caracteres válidos porque los
identificadores (también conocidos como nombres) son los mismos que en Python 2.x: las
letras mayúsculas y minúsculas de la A a la
Z, el guión bajo _ y, excepto el primer carácter, los dígitos del 0 al 9.
Según la regla, no podemos declarar una variable con el nombre 2x. Comparado con Python, en R
también podemos usar. En los nombres de las variables17. Sin embargo, existe una solución alternativa
para usar nombres de variables no válidos en R con la ayuda de ''.

Ahora está claro que el uso de '' en '+.Complex' es para definir una función con un nombre no válido.
Colocar .Complex después de + está relacionado con el envío del método S3, que no se tratará aquí.
1.7.1 Accesibilidad de miembros en Python
En algunos lenguajes de programación, los miembros (variables o métodos) de una clase se pueden
declarar con modificadores de acceso que especifican la accesibilidad o el alcance de un miembro. En
Python, los miembros de la clase no tienen modificadores de acceso explícitos, pero aún es posible
especificar la accesibilidad. De forma predeterminada, se puede acceder al miembro de la clase dentro
o fuera de la definición de la clase. Si el nombre del miembro comienza con un solo guión bajo, no se
debe acceder al miembro fuera de la definición de clase por convención, pero no se aplica. Si el nombre
del miembro comienza con un guión bajo doble, el nombre del miembro está alterado y, por lo tanto, el
miembro original no puede acceder al miembro fuera de la definición de clase. Pero dentro de la
definición de clase siempre se puede acceder a estas variables. Veamos el ejemplo a continuación.
Python
1 >>> class ProtectedMemberClass:
2 ... """
3 ... _x and _func1 are protected and are recommended not to be accessed out of the class definition, but not
enforced.
4 ... __y and __func2 are private and the names would be mangled
5 ... """
6 ... def __init__(self, val1, val2):
7 ... self._x = val1
8 ... self.__y = val2
9 ... def _func1(self):
10 ... print("protected _func1 called")
11 ... def __func2(self):
12 ... print("private __func2 called")
13 ... def func3(self):
14 ... # inside the class definition, we can access all these members
15 ... print("self._x is {0} and self.__y is{1}".format(
16 ... self._x, self.__y))
17 ... self._func1()
18 ... self.__func2()
19 ...
20 >>> p = ProtectedMemberClass(0, 1)
21 >>> p._x
22 0
23 >>> p.__y
24 Traceback (most recent call last):
25 File "", line 1, in
26 AttributeError: ’ProtectedMemberClass’ object has no attribute ’ __y’
27 >>> p._func1()
28 protected _func1 called
29 >>> p.__func2()
30 Traceback (most recent call last):
31 File "", line 1, in
32 AttributeError: ’ProtectedMemberClass’ object has no attribute ’ __func2’
33 >>> p.func3()
34 self._x is 0 and self.__y is1
35 protected _func1 called
36 private __func2 called
En este ejemplo, se arroja un error cuando intentamos acceder a __y o __func2 fuera de la definición de clase.
Pero son accesibles dentro de la definición de clase y estos campos se denominan normalmente campos
privados.
1.8 VARIOS
Hay algunos elementos que no he discutido hasta ahora, que también son importantes para dominar R/Python.
1.8.1 Instalación del paquete/módulo
• Usar la función install.packages() en R
• Usar R IDE para instalar paquetes
• Use pip para instalar módulos en Python
1.8.2 Entorno virtual
El entorno virtual es una herramienta para administrar dependencias en Python. Hay diferentes formas de
crear entornos virtuales en Python. Sugiero usar el módulo venv incluido con Python 3. Desafortunadamente,
no hay nada como un entorno virtual real en R, que yo sepa, aunque hay bastantes herramientas/paquetes de
administración.
1.8.3 <- frente a =
Si conoce R antes, probablemente haya oído hablar del consejo19 de usar
Más sobre R/Python Programación
Aprendimos algunos de los conceptos básicos de la programación de R/Python en el
capítulo anterior. Espero que este capítulo pueda usarse como un tutorial de
programación R/Python de nivel intermedio. Hay algunos temas que cubrir, incluida la
depuración, la vectorización y algunas otras funciones útiles de R/Python.
I.1 TRABAJAR CON SCRIPTS R/PYTHON
En el Capítulo 1 estábamos codificando dentro del modo interactivo de R/Python. Cuando estamos trabajando
en proyectos del mundo real, usar un entorno de desarrollo integrado (IDE) es una opción más pragmática. No
hay muchas opciones para R IDE, y entre ellas RStudio1 es la mejor que he usado hasta ahora. En cuanto a
Python, recomendaría Visual Studio Code2 o PyCharm3. Pero, por supuesto, puede usar cualquier editor de
texto para escribir scripts R/Python.
¡Escribamos un script simple para imprimir Hello World! en R/Python. Hice un directorio chapter2 en mi disco,
el script R se crea con un editor de texto y luego se guarda como hello_world.R y el script de Python se crea y
se guarda como hello_world. py, dentro del directorio.

Hay algunas formas de ejecutar un script R. Por ejemplo, podemos ejecutar el script desde la consola con el
comando r −f filename . También podemos usar el comando de nombre de archivo Rscript desde la consola. Si
usamos r −f filename para ejecutar un script, el contenido del script también se imprime. Con el nombre de
archivo Rscript, el contenido del script no sería impreso. Además, podemos abrir la sesión interactiva de R y
usar la función source() . 6 Ejecutemos estos dos scripts para ver los resultados. En cuanto al script de Python,
podemos ejecutarlo desde la consola
A continuación se muestra otro ejemplo de secuencias de comandos, en el que primero creamos una función
para imprimir un triángulo y luego llamamos a la función para imprimir

Ejecutemos estos dos scripts para ver los resultados


La principal razón para usar scripts es que no queremos limitarnos a usar R/Python como calculadoras básicas.
En un script, podemos poner tantas líneas de código R/Python como necesitemos. En Python, un script también
se conoce como módulo. Es común ver un proyecto que consta de múltiples scripts para organizar mejor la
estructura del código. Por ejemplo, en un proyecto de ciencia de datos, las funciones de carga/limpieza de
datos podrían organizarse dentro de un script y las funciones de creación de modelos podrían organizarse en
otro script.
La prueba/validación de modelos también puede tener su propio script. También es válido poner diferentes
scripts en directorios. Por ejemplo, a continuación se muestra la estructura de directorios de un proyecto de
juguete.
toy_project/
|-- src
| |-- data_processing
| | |-- data_clean.R
| | |__ data_clean.py
| |-- model_building
| | |-- linear_model.R
| | |-- linear_model.py
| | |-- tree_model.R
| | |__ tree_model.py
| |__ model_validation
|__ test

Podríamos poner secuencias de comandos en estos directorios en consecuencia. Tenga en cuenta que el
proyecto de juguete es solo para ilustración y sus proyectos no necesitan seguir esta estructura de proyecto de
juguete.
Un problema inmediato de poner funciones en diferentes scripts es averiguar cómo R/Python podría
encontrar/usar una función definida en otro script. Por ejemplo, en el script linear_model.R necesitamos llamar
a una función de imputación definida en data_clean .R. La solución es la función fuente en R y la declaración de
importación en Python. En R, obtener un archivo es sencillo usando la ruta del archivo. Sin embargo, hay
algunas advertencias al usar fuente en Python, que están fuera del alcance de este libro.
Cuando usamos un IDE (RStudio o Visual Studio Code), no solo podemos trabajar en scripts, sino que también
podemos trabajar en proyectos. Pero un proyecto no tiene nada que ver con el lenguaje de programación en sí.
Un proyecto es específico de IDE. Por ejemplo, con RStudio podemos crear un proyecto de RStudio para
organizar los scripts así como algunas configuraciones4.
Para trabajar con un proyecto específico de IDE, se debe instalar el IDE. Pero ejecutar los scripts no requiere
ningún IDE, ya que incluso podemos ejecutar los scripts con una interfaz de línea de comandos.
I.2 DEPURACIÓN EN R/PYTHON
La depuración es uno de los aspectos más importantes de la programación. ¿Qué es la depuración en
programación? Los programas que escribimos pueden incluir errores/errores y la depuración es un proceso
paso a paso para encontrar y eliminar los errores/errores para obtener los resultados deseados.
Si es lo suficientemente inteligente o los errores son lo suficientemente evidentes, puede depurar el programa
en su mente sin usar una computadora. Pero, en general, necesitamos algunas herramientas/técnicas que nos
ayuden con la depuración.
I.2.1 Imprimir
La mayoría de los lenguajes de programación brindan la funcionalidad de impresión, que es una forma natural
de depuración. Al tratar de colocar declaraciones de impresión en diferentes posiciones, finalmente podemos
detectar los errores. Cuando utilizo la impresión para depurar, es como jugar al Buscaminas. En Python, hay un
módulo llamado logging5 que podría usarse para depurar como la función de impresión , pero de una manera
más elegante.
I.2.2 Navegador en R y pdb en Python
En R, existe una función browser() que interrumpe la ejecución y permite la inspección del entorno actual. De
manera similar, hay un módulo pdb en Python que proporciona más funciones de depuración. Solo nos
centraremos en los usos básicos del navegador () y la función set_trace() en el módulo pdb . La diferencia
esencial entre la depuración usando print() y browser() y set_trace() es que la última función nos permite
depurar en un modo interactivo.
Escribamos una función que tome un vector/lista ordenada v y un valor objetivo x como entrada y devuelva el
índice más a la izquierda pos del vector/lista ordenado para que v[pos]>=x.
Dado que v ya está ordenado, podemos simplemente recorrerlo de izquierda a derecha para encontrar
posición
Ahora vamos a ejecutar estos dos scripts.

Cuando x=11, la función devuelve NULL en R y None en Python porque no existe tal elemento en v mayor que
x. La implementación anterior es trivial, pero no eficiente. Si tiene experiencia en estructuras de datos y
algoritmos, probablemente sepa que esta pregunta se puede resolver mediante la búsqueda binaria. La idea
esencial de la búsqueda binaria proviene del concepto de divide y vencerás6. Dado que v ya está ordenada,
podemos dividirla en dos particiones cortándola por la mitad, y luego obtenemos la partición izquierda y la
partición derecha. v está ordenado, lo que implica que tanto la partición izquierda como la partición derecha
también están ordenadas. Si el valor objetivo x es mayor que el elemento más a la derecha en la partición
izquierda, podemos simplemente descartar la partición izquierda y buscar x dentro de la partición derecha. De
lo contrario, podemos descartar la partición derecha y buscar x dentro de la partición izquierda. Una vez que
hayamos determinado en qué partición buscar, podemos aplicar la idea recursivamente de modo que en cada
paso reduzcamos el tamaño de v a la mitad. Si la longitud 7 de v se denota como n, en términos de O grande,
búsqueda la complejidad del tiempo de ejecución de la de notación binaria es O (log n), en comparación con O
(n) de la implementación del bucle for. El siguiente código implementa la solución de búsqueda binaria a
nuestra pregunta. (Es mas intuitivo hacerlo con recursion pero aqui lo escribo con iteracion ya que tail
recursion , Ahora vamos a ejecutar estos dos scripts. no se admite la optimización8 en R/Python).

R capítulo 2/find_binary_search_buggy.R
1 binary_search_buggy=function(v, x){
2 start = 1
3 end = length(v)
4 while (start =x){ < end){
5 mid = (start+end) %/% 2 # %/% is the floor division operator
6 if (v[mid]>=x){
7 end = mid
8 }else{
9 start = mid+1
10 }
11 }
12 return(start)
13 }
14 v=c(1,2,5,10)
15 print(binary_search_buggy(v,−1))
16 print(binary_search_buggy(v,5))
17 print(binary_search_buggy(v,11))

Python capítulo 2/find_binary_search_buggy_debug.py


1 def binary_search_buggy(v, x):
2 start, end = 0, len(v)−1
3 while start=x:
6 end = mid
7 else:
8 start = mid+1
9 return start
10
11 v=[1,2,5,10]
12 print(binary_search_buggy(v,−1))
13 print(binary_search_buggy(v,5))
14 print(binary_search_buggy(v,11))

Ahora ejecutemos estos dos scripts binary_search.

Las soluciones de búsqueda binaria no funcionan como se esperaba cuando x=11. Escribimos dos nuevos
guiones.
R capítulo 2/find_binary_search_buggy_debug.R
1 binary_search_buggy=function(v, x){
2 browser()
3 start = 1
4 end = length(v)
5 while (start <end){
6 mid = (start+end)
7 if (v[mid]>=x){
8 end = mid
9 }else{
10 start = mid+1
11 }
12 }
13 return(start)
14 }
15 v=c(1,2,5,10)
16 print(binary_search_buggy(v, 11))

Python capítulo 2/find_binary_search_buggy_debug.py


1 from pdb import set_trace
2 def binary_search_buggy(v, x):
3 set_trace()
4 start, end = 0, len(v)−1
5 while start <end:
6 mid = (start+end)//2
7 if v[mid]>=x:
8 end = mid
9 else:
10 start = mid+1
11 return start
12
13 v=[1,2,5,10]
14 print(binary_search_buggy(v,11))

Intentemos depurar los programas con la ayuda de browser() y set_trace().


R
1 > source(’binary_search_buggy_debug.R’)
2 Called from: binary_search_buggy(v, 11)
3 Browse[1]> ls()
4 [1] "v" "x"
5 Browse[1]> n
6 debug at binary_search_buggy_debug.R#3: start = 1
7 Browse[2]> n
8 debug at binary_search_buggy_debug.R#4: end = length(v)
9 Browse[2]> n
10 debug at binary_search_buggy_debug.R#5: while (start < end) {
11 mid = (start + end)%/%2
12 if (v[mid] >= x) {
13 end = mid
14 }
15 else {
16 start = mid + 1
17 }
18 }
19 Browse[2]> n
20 debug at binary_search_buggy_debug.R#6: mid = (start + end)%/%2
21 Browse[2]> n
22 debug at binary_search_buggy_debug.R#7: if (v[mid] >= x) {
23 end = mid
24 } else {
25 start = mid + 1
26 }
27 Browse[2]> n
28 debug at binary_search_buggy_debug.R#10: start = mid + 1
29 Browse[2]> n
30 debug at binary_search_buggy_debug.R#5: (while) start < end
31 Browse[2]> n
32 debug at binary_search_buggy_debug.R#6: mid = (start + end)%/%2
33 Browse[2]> n
34 debug at binary_search_buggy_debug.R#7: if (v[mid] >= x) {
35 end = mid
36 } else {
37 start = mid + 1
38 }
39 Browse[2]> n
40 debug at binary_search_buggy_debug.R#10: start = mid + 1
41 Browse[2]> n
42 debug at binary_search_buggy_debug.R#5: (while) start < end
43 Browse[2]> start
44 [1] 4
45 Browse[2]> n
46 debug at binary_search_buggy_debug.R#13: return(start)
47 Browse[2]> n
48 [1] 4
En el fragmento de código R anterior, colocamos la función browser() en la parte superior de la función
binary_search_buggy. Luego, cuando llamamos a la función, ingresamos al entorno de depuración. Al llamar a
ls() vemos todas las variables en el ámbito de depuración actual, es decir, v, x. Escribir n evaluará la siguiente
declaración. Después de escribir n varias veces, finalmente salimos del bucle while porque start = 4 tal que start
< end es falso
Como resultado, la función solo devuelve el valor de inicio, es decir, 4. Para salir del entorno de depuración,
podemos escribir Q; para continuar la ejecución podemos teclear c. La causa raíz es que no tratamos el caso de
la esquina cuando el valor objetivo x es más grande que el último/elemento más grande en v correctamente.

Depuremos la función Python usando el módulo pdb.

Python

1 chapter2 $python3.7 binary_search_buggy_debug.py


2 > chapter2/binary_search_buggy_debug.py(4)binary_search_buggy()
3 −> start,end = 0,len(v)−1
4 (Pdb) n
5 > chapter2/binary_search_buggy_debug.py(5)binary_search_buggy()
6 −> while start while start=x:
7 (Pdb) l
8 1 from pdb import set_trace
9 2 def binary_search_buggy(v,x):
10 3 set_trace()
11 4 start,end = 0,len(v)−1
12 5 −> while start=x:
13 6 mid = (start+end)//2
14 7 if v[mid]>=x:
15 8 end = mid
16 9 else:
17 10 start = mid+1
18 11 return start
19 (Pdb) b 7
20 Breakpoint 1 at chapter2/binary_search_buggy_debug.py:7
21 (Pdb) c
22 > chapter2/binary_search_buggy_debug.py(7)binary_search_buggy()
23 −> if v[mid]>=x:
24 (Pdb) c
25 > chapter2/binary_search_buggy_debug.py(7)binary_search_buggy()
26 −> if v[mid]>=x:
27 (Pdb) mid
28 2
29 (Pdb) n
30 > chapter2/binary_search_buggy_debug.py(10) binary_search_buggy()
31 −> start = mid+1
32 (Pdb) n
33 > chapter2/binary_search_buggy_debug.py(5)binary_search_buggy()
34 −> while start
35 (Pdb) start
36 3
37 (Pdb) n
38 > chapter2/binary_search_buggy_debug.py(11) binary_search_buggy()
39 −> return start

Similar a R, el comando n evaluaría la siguiente declaración en pdb. Escribir el comando l mostraría la línea
actual de la ejecución actual. El comando b line_number establecería la línea correspondiente como un punto
de ruptura; y c continuaría la ejecución hasta el próximo punto de interrupción (si existe).
En R, además de la función browser (), hay un par de funciones debug() y undebug() que también son muy
útiles cuando intentamos depurar una función; especialmente cuando la función está envuelta en un paquete.
Más específicamente, la función de depuración invocará el entorno de depuración cada vez que llamemos a la
función para depurar. Vea el siguiente ejemplo que explica cómo invocamos el entorno de depuración para la
función sd (cálculo de la desviación estándar).

R
1 > x=c(1,1,2)
2 > debug(sd)
3 > sd(x)
4 debugging in: sd(x)
5 debug: sqrt(var(if (is.vector(x) || is.factor(x)) x else as. double(x),
6 na.rm = na.rm))
7 Browse[2]> ls()
8 [1] "na.rm" "x"
9 Browse[2]> Q
10 > undebug(sd)
11 > sd(x)
12 [1] 0.5773503

Las soluciones binary_search se corrigen a continuación.


R capítulo2/find_binary_search.R
1 binary_search=function(v,x){
2 if (x>v[length(v)]){return(NULL)}
3 start = 1
4 end = length(v)
5 while (start =x){
6 mid = (start+end)
7 if (v[mid]>=x){
8 end = mid
9 }else{
10 start = mid+1
11 }
12 }
13 return(start)
14 }
Python capítulo 2/find_binary_search.py
1 def binary_search(v, x):
2 if x>v[−1]: return
3 start, end = 0, len(v)−1
4 while start=x:
5 mid = (start+end)//2
6 if v[mid]>=x:
7 end = mid
8 else:
9 start = mid+1
10 return start

2.3 COMPARACIÓN
Por evaluación comparativa, me refiero a medir todo el tiempo de operación de un programa.

Hay otro término llamado elaboración de perfiles que está relacionado con la evaluación comparativa. Pero la
creación de perfiles es más compleja, ya que comúnmente tiene como objetivo comprender el
comportamiento del programa y optimizar el programa en términos de tiempo transcurrido durante la
operación.

En R, me gusta usar el paquete microbenchmark . Y en Python, el módulo timeit es una buena herramienta
para usar cuando queremos comparar pequeños fragmentos de código Python.

Como se mencionó anteriormente, la complejidad del tiempo de ejecución de la búsqueda binaria es mejor eso
que una búsqueda de bucle for. Podemos hacer benchmarking para comparar los dos algoritmos.

R capítulo2/punto de referencia.R

1 library(microbenchmark)

2 source(’binary_search.R’)

3 source(’find_pos.R’)

5 v=1:10000

7 # call each function 1000 times;

8 # each time we randomly select an integer as the target value

10 # for−loop solution

11 set.seed(2019)

12 print(microbenchmark(find_pos(v, sample(10000,1)),times=1000))

13 # binary−search solution
14 set.seed(2019)

15 print(microbenchmark(binary_search(v, sample(10000,1)),times =1000))

En el código R anterior, times=1000 significa que queremos llamar a la función 1000 veces en el proceso de
evaluación comparativa. La función sample() se utiliza para extraer muestras de un conjunto de elementos.
Específicamente, pasamos el argumento 1 a sample() para dibujar un solo elemento. Es la primera vez que
usamos la función set.seed() en este libro. En R/Python, dibujamos números aleatorios basados en el algoritmo
del generador de números pseudoaleatorios (PRNG)9. La secuencia de números generados por PRNG se
completa determinada por un valor inicial, es decir, la semilla. Siempre que un programa involucre el uso de
PRNG, es mejor configurar la semilla para obtener resultados replicables (vea el ejemplo a continuación).

Ahora ejecutemos el script R para ver el resultado de la evaluación comparativa.

1 > source(’benchmark.R’)

2 Unit: microseconds

3 expr min lq mean median uq max

4 find_pos(v, sample(10000, 1)) 3.96 109.5385 207.6627 207.5565 307.8875 536.171

5 neval

6 1000

7 Unit: microseconds

8 expr min lq mean median uq max

9 binary_search(v, sample(10000, 1)) 5.898 6.3325 14.2159 6.6115 7.3635 6435.57

10 neval

11 1000
La solución binary_search es mucho más eficiente según el resultado de la evaluación comparativa. Haciendo lo
mismo

La evaluación comparativa en Python es un poco complicada.

Python capítulo 2/benchmark.py

1 from binary_search import binary_search


2 from find_pos import find_pos
3 import timeit
4 import random
5
6 v=list(range(1,10001))
7
8 def test_for_loop(n):
9 random.seed(2019)
10 for _ in range(n):
11 find_pos(v, random.randint(1,10000))
12
13 def test_bs(n):
14 random.seed(2019)
15 for _ in range(n):
16 binary_search(v, random.randint(1,10000))
17
18 # for−loop solution
19 print(timeit.timeit(’test_for_loop(1000)’,setup=’from __main__ import test_for_loop’,number=1))
20 # binary_search solution
21 print(timeit.timeit(’test_bs(1000)’,setup=’from __main__ import test_bs’,number=1))
La parte más interesante del código de Python anterior es de __main__ import.
Ignorémoslo por ahora; lo revisaremos más tarde.
A continuación se muestra el resultado de la evaluación comparativa en Python (la unidad es la segunda).
Python
1 chapter2 $python3 benchmark.py
2 0.284618441
3 0.00396658900000002

I.3 VECTORIZACIÓN
En computación paralela, la vectorización automática10 significa que un programa en una implementación
escalar se convierte en una implementación vectorial que procesa múltiples pares de operandos
simultáneamente por compiladores que cuentan con vectorización automática. Por ejemplo, calculemos la
suma de elementos de dos matrices x e y de la misma longitud en el lenguaje de programación C.

1 int x[4] = {1,2,3,4};


2 int y[4] = {0,1,2,3};
3 int z[4];
4 for (int i=0;i
5 z[i]=x[i]+y[i];
6}

El compilador puede vectorizar el código C anterior para que el número real de iteraciones realizadas sea
inferior a 4. Si se procesan 4 pares de operandos a la vez, solo habrá 1 iteración. La vectorización automática
puede hacer que el programa se ejecute mucho más rápido en algunos lenguajes como C. Sin embargo, cuando
hablamos de vectorización en R/ Python, es diferente de la vectorización automática. La vectorización en
R/Python generalmente se refiere al esfuerzo humano que se paga para evitar los bucles for. Primero, veamos
algunos ejemplos de cómo los bucles for pueden ralentizar sus programas en R/Python.

R capítulo2/vectorización_1.R
1 library(microbenchmark)
2
3 # generate n standard normal r.v
4 rnorm_loop = function(n){
5 x=rep(0,n)
6 for (i in 1:n) {x[i]=rnorm(1)}
7}
8
9 rnorm_vec = function(n){
10 x=rnorm(n)
11 }
12
13 n=100
14 # for loop
15 print(microbenchmark(rnorm_loop(n),times=1000))
16 # vectorize
17 print(microbenchmark(rnorm_vec(n),times=1000))

Ejecutar el código R produce el siguiente resultado en mi máquina local.


R
1 > source(’vectorization_1.R’)
2 Unit: microseconds
3 expr min lq mean median uq max neval
4 rnorm_loop(n) 131.622 142.699 248.7603 145.3995 270.212 16355.6 1000
5 Unit: microseconds
6 expr min lq mean median uq max neval
7 rnorm_vec(n) 6.696 7.128 10.87463 7.515 8.291 2422.338 1000

Python
1 import timeit
2 import numpy as np
3
4 def rnorm_for_loop(n):
5 x=[0]∗n # create a list with n 0s
6 np.random.seed(2019)
7 for _ in range(n):
8 np.random.normal(0,1,1)
9
10 def rnorm_vec(n):
11 np.random.seed(2019)
12 x = np.random.normal(0,1,n)
13
14 print("for loop")
15 print(f’{timeit.timeit("rnorm_for_loop(100)",setup="from __main__ import
rnorm_for_loop",number=1000):.6f} seconds’)
16 print("vectorized")
17 print(f’{timeit.timeit("rnorm_vec(100)",setup="from __main__ import rnorm_vec",number=1000):.6f}
seconds’)

Tenga en cuenta que en este ejemplo de Python estamos usando el submódulo aleatorio del módulo numpy en
lugar del módulo aleatorio incorporado, ya que el módulo aleatorio no proporciona la versión vectorizada de la
función de generación de números aleatorios. Ejecutar el código de Python da como resultado lo siguiente en
mi máquina local.

Python
1 chapter2 $python3.7 vectorization_1.py
2 for loop
3 0.258466 seconds
4 vectorized
5 0.008213 seconds
Además, timeit.timeit mide el tiempo total para ejecutar las declaraciones principales varias veces, pero no el
tiempo promedio por ejecución. En R o Python, la versión vectorizada de la variable aleatoria normal aleatoria
(rv) es significativamente más rápida que la versión escalar. Vale la pena notar el uso de la instrucción print(f'')
en el código de Python, que es diferente de la forma en que imprimimos el objeto de la clase Complex en el
Capítulo 1. En el código anterior, usamos la cadena f que es una cadena literal precedida por 'f' que contiene
expresiones dentro de {} que se reemplazarían con sus valores. f−string es una característica introducida desde
Python 3.6.
También vale la pena señalar que muchas funciones integradas en R ya están vectorizadas, como los
operadores aritméticos básicos, los operadores de comparación, ifelse() , los operadores lógicos de elementos
&,|. Pero los operadores lógicos &&, || no están vectorizados.
Además de la vectorización, también hay algunas funciones integradas que pueden ayudar a evitar el uso de
bucles for. Por ejemplo, en R podríamos usar la familia de funciones apply para reemplazar bucles for; y en
Python la función map() también puede ser útil. En el módulo pandas de Python , también hay muchos usos de
los métodos map/apply . Pero, en general, el uso de funciones de aplicación/mapa tiene poco o nada que ver
con la mejora del rendimiento. Sin embargo, los usos apropiados de tales funciones pueden ayudar con la
legibilidad del programa. En comparación con la familia de funciones apply de R, creo que la función do.call() es
más útil en la práctica. Pasaremos algún tiempo en do.call() más tarde.

Aplicación: modelo de tráfico de Biham–Middleton–Levine (BML)


Teniendo en cuenta la importancia de la vectorización en la programación científica, intentemos
familiarizarnos más con la vectorización a través del modelo de tráfico Biham-Middleton-Levine
(BML). El modelo BML es muy importante en los estudios modernos de flujo de tráfico, ya que muestra
una transición de fase brusca desde un estado de flujo libre a un estado de atasco total. Un modelo
BML simplificado podría caracterizarse de la siguiente manera

 Inicializado en una red 2-D, cada sitio del cual está vacío u ocupado por una partícula coloreada (azul o
rojo)
 Las partículas se distribuyen aleatoriamente a través de la inicialización según una distribución uniforme;
los dos colores de las partículas están igualmente distribuidos.
 En pasos de tiempo pares, todas las partículas azules intentan mover un sitio hacia arriba y el
intento falla si el sitio a ocupar no está vacío;
 En pasos de tiempo impares, todas las partículas rojas intentan moverse un sitio a la derecha y el
intento falla si el sitio a ocupar no está vacío;
 Se supone que la red es periódica, lo que significa que cuando una partícula sale de la red, entrará
en la red desde el lado opuesto.
El modelo BML especificado anteriormente se implementa en R/Python de la siguiente manera para ilustrar el
uso de la vectorizació.

R capítulo 2/BM

1 library(R6)
2 BML = R6Class(
3 "BML",
4 public = list(
5 # alpha is the parameter of the uniform distribution to control particle distribution’s density
6 # m∗n is the dimension of the lattice
7 alpha = NULL ,
8 m = NULL ,
9 n = NULL ,
10 lattice = NULL ,
11 initialize = function(alpha, m, n) {
12 self$alpha = alpha
13 self$m=m
14 self$n=n
15 self$initialize_lattice()
16 },
17 initialize_lattice = function() {
18 # 0 −> empty site
19 # 1 −> blue particle
20 # 2 −> red particle
21 u = runif(self$m ∗ self$n)
22 # the usage of L is to make sure the elements in particles are of type integer;
23 # otherwise they would be created as double
24 particles = rep(0L, self$m ∗ self$n)
25 # doing inverse transform sampling
26 particles[(u > self$alpha) &
27 (u <= (self$alpha + 1.0) / 2)] = 1L
28 particles[u > (self$alpha + 1.0) / 2] = 2L
29 self$lattice = array(particles, c(self$m, self$n))
30 },
31 odd_step = function() {
32 blue.index = which(self$lattice == 1L, arr.ind = TRUE)
33 # make a copy of the index
34 blue.up.index = blue.index
35 # blue particles move 1 site up
36 blue.up.index[, 1] = blue.index[, 1] − 1L
37 # periodic boundary condition
38 blue.up.index[blue.up.index[, 1] == 0L, 1] = self$m
39 # find which moves are feasible
40 blue.movable = self$lattice[blue.up.index] == 0L
41 # move blue particles one site up
42 # drop=FALSE prevents the 2D array degenerates to 1D array
43 self$lattice[blue.up.index[blue.movable, , drop = FALSE]] = 1L
44 self$lattice[blue.index[blue.movable, , drop = FALSE]] = 0 L
45 },
46 even_step = function() {
47 red.index = which(self$lattice == 2L, arr.ind = TRUE)
48 # make a copy of the index
49 red.right.index = red.index
50 # red particles move 1 site right
51 red.right.index[, 2] = red.index[, 2] + 1L
52 # periodic boundary condition
53 red.right.index[red.right.index[, 2] == (self$n + 1L), 2] = 1
54 # find which moves are feasible
55 red.movable = self$lattice[red.right.index] == 0L
56 # move red particles one site right
57 self$lattice[red.right.index[red.movable, , drop = FALSE]] = 2L
58 self$lattice[red.index[red.movable, , drop = FALSE]] = 0L
59 }
60 )
61 )

Ahora podemos crear un sistema BML simple en una red de 5 × 5 utilizando el código R anterior
R
1 > source(’BML.R’)
2 > set.seed(2019)
3 > bml=BML$new(0.4,5,5)
4 > bml$lattice
5 [,1] [,2] [,3] [,4] [,5]
6 [1,] 2 0 2 1 1
7 [2,] 2 2 1 0 1
8 [3,] 0 0 0 2 2
9 [4,] 1 0 0 0 0
10 [5,] 0 1 1 1 0
11 > bml$odd_step()
12 > bml$lattice
13 [,1] [,2] [,3] [,4] [,5]
14 [1,] 2 0 2 1 0
15 [2,] 2 2 1 0 1
16 [3,] 1 0 0 2 2
17 [4,] 0 1 1 1 0
18 [5,] 0 0 0 0 1
19 > bml$even_step()
20 > bml$lattice
21 [,1] [,2] [,3] [,4] [,5]
22 [1,] 0 2 2 1 0
23 [2,] 2 2 1 0 1
24 [3,] 1 0 0 2 2
25 [4,] 0 1 1 1 0
26 [5,] 0 0 0 0 1

En el paso de inicialización, utilizamos el enfoque de muestreo por inversión13 para generar el estado de cada
sitio. El método de muestreo por inversión es un enfoque básico pero poderoso para generar rv a partir de
cualquier distribución de probabilidad dada su función de distribución acumulativa (CDF). Hablaremos más
sobre la técnica de muestreo en el Capítulo 4.
Python
1 import numpy as np
2
3 class BML:
4 def __init__(self, alpha, m, n):
5 self.alpha = alpha
6 self.shape = (m, n)
7 self.initialize_lattice()
8
9 def initialize_lattice(self):
10 u = np.random.uniform(0.0, 1.0, self.shape)
11 # instead of using default list, we use np.array to create the lattice
12 self.lattice = np.zeros_like(u, dtype=int)
13 # the parentheses below can’t be ignored
14 self.lattice[(u > self.alpha) & (u <= (1.0+self.alpha) /2.0)] = 1
15 self.lattice[u > (self.alpha+1.0)/2.0] = 2
16
17 def odd_step(self):
18 # please note that np.where returns a tuple which is immutable
19 blue_index = np.where(self.lattice == 1)
20 blue_index_i = blue_index[0] – 1
21 blue_index_i[blue_index_i < 0] = self.shape[0]−1
22 blue_movable = self.lattice[(blue_index_i , blue_index [1])] == 0
23 self.lattice[(blue_index_i[blue_movable],
24 blue_index[1][blue_movable])] = 1
25 self.lattice[(blue_index[0][blue_movable],
26 blue_index[1][blue_movable])] = 0
27
28 def even_step(self):
29 red_index = np.where(self.lattice == 2)
30 red_index_j = red_index[1] + 1
31 red_index_j[red_index_j == self.shape[1]] = 0
32 red_movable = self.lattice[(red_index[0], red_index_j)] == 0
33 self.lattice[(red_index[0][red_movable],
34 red_index_j[red_movable])] = 2
35 self.lattice[(red_index[0][red_movable],
36 red_index[1][ red_movable])] = 0

También se proporciona la implementación de Python.


R
1 >>> import numpy as np
2 >>> np.random.seed(2019)
3 >>> from BML import BML
4 >>> bml=BML(0.4,5,5)
5 >>> bml.lattice
6 array([[2, 0, 1, 1, 2],
7 [0, 2, 2, 2, 1],
8 [1, 0, 0, 2, 0],
9 [2, 0, 1, 0, 2],
10 [1, 1, 0, 2, 1]])
11 >>> bml.odd_step()
12 >>> bml.lattice
13 array([[2, 0, 0, 1, 2],
14 [1, 2, 2, 2, 1],
15 [0, 0, 1, 2, 0],
16 [2, 1, 0, 0, 2],
17 [1, 0, 1, 2, 1]])
18 >>> bml.even_step()
19 >>> bml.lattice
20 array([[0, 2, 0, 1, 2],
21 [1, 2, 2, 2, 1],
22 [0, 0, 1, 0, 2],
23 [2, 1, 0, 0, 2],
24 [1, 0, 1, 2, 1]])

Tenga en cuenta que aunque hemos importado numpy en BML.py, lo importamos nuevamente en el código
anterior para establecer la semilla aleatoria. Si cambiamos la línea a desde BML import , no necesitamos
importar numpy nuevamente. Pero no se recomienda importar * desde un módulo.

I.4 VERGONZOSAMENTE PARALELISMO EN R/PYTHON


De acuerdo con la explicación en wikipedia14, el subproceso único es el procesamiento de un comando a la
vez, y su opuesto es el subproceso múltiple. Un proceso es la instancia de un programa informático ejecutado
por uno o varios hilos15. Multihilo no es lo mismo que paralelismo. En un entorno de un solo procesador, el
procesador puede cambiar la ejecución entre subprocesos, lo que da como resultado una ejecución
simultánea. Sin embargo, es posible que un proceso con subprocesos múltiples se ejecute en un entorno
multiprocesador y cada uno de los subprocesos en un procesador independiente, lo que da como resultado
una ejecución en paralelo.
Tanto R como Python son de un solo subproceso. En Python, hay un paquete de subprocesos age16, que
admite subprocesos múltiples en un solo núcleo. Puede adaptarse a algunas tareas específicas. Por ejemplo, en
web scraping, los subprocesos múltiples en un solo núcleo pueden acelerar el programa si el tiempo de
descarga excede el tiempo de procesamiento de la CPU.
Ahora hablemos del vergonzosamente paralelismo por multiprocesamiento. Un problema vergonzosamente
paralelo es aquel en el que se necesita poco o ningún esfuerzo para separar el problema en varias tareas
paralelas17. En R hay varios paquetes que admiten multiprocesamiento en varios núcleos de CPU, por ejemplo,
el paquete paralelo , que es mi favorito. En Python, también hay algunos módulos disponibles, como
multiprocesamiento, joblib y concurrent.futures
Veamos una aplicación del vergonzosamente paralelismo para calcular π usando la simulación de Monte
Carlo18

Aplicación: simulación de Monte Carlo para estimar π mediante paralelización


La simulación de Monte Carlo proporciona una forma simple y directa de estimar π. Sabemos que el área de un
círculo con radio 1 es solo π. Así, podemos convertir el problema original del cálculo de π en un nuevo
problema, es decir, cómo calcular el área de un círculo con radio 1. También sabemos que el área de un
cuadrado con lado de longitud 2 es igual a 4. Por lo tanto, π se puede calcular como 4rc/s donde rc/s denota la
razón de áreas de un círculo con radio 1 y un cuadrado con longitud de lado 2. Ahora el problema es ¿cómo
calcular la razón rc/s? Cuando tiramos al azar
Figura 2.1: Genere puntos dentro de un cuadrado y cuente cuántas veces estos puntos caen dentro del círculo
inscrito
n puntos en el cuadrado y m de estos puntos caen en el círculo inscrito (ver Figura 2.1),
entonces podemos estimar la relación como m/n. Como resultado, una estimación natural
de π es 4m/n. Este problema es un problema vergonzosamente paralelo por su naturaleza.
Veamos cómo implementamos la idea en R/Python.
R capítulo 2/pi.R
1 library(parallel)
2 count_inside_point = function(n){
3 m=0
4 for (i in 1:n){
5 p_x = runif(1, −1, 1)
6 p_y = runif(1, −1, 1)
7 if (p_x^2 + p_y^2 <=1){
8 m = m+1
9}
10 }
11 m
12 }
13
14 # now let’s use the mcapply for parallelization
15 generate_points_parallel = function(n){
16 # detectCores() returns the number of cores available
17 # we assign the task to each core
18 unlist(mclapply(X = rep(n %/% detectCores(), detectCores()), FUN=count_inside_point))
19 }
20
21 # now let’s use vectorization
22 generate_points_vectorized = function(n){
23 p = array(runif(n∗2,−1,1), c(n,2))
24 sum((p[,1]^2+p[,2]^2) <=1)
25 }
26
27 pi_naive = function(n) cat(’naive: pi −’, 4∗count_inside_point(n )/n, ’\n’)
28
29 pi_parallel = function(n) cat(’parallel: pi −’, 4∗sum( generate_points_parallel(n))/n, ’\n’)
30
31 pi_vectorized = function(n) cat(’vectorized: pi −’, 4∗sum( generate_points_vectorized(n))/n, ’\n’)

En el fragmento de código R anterior, usamos la función mclapply , que actualmente no está disponible en
algunos sistemas operativos19. Cuando no esté disponible, podemos considerar usar parLapply en su lugar.
Python capítulo 2/pi.by
1 # now let’s try the parallel approach
2 # each process uses the same seed, which is not desired
3 def generate_points_parallel(n):
4 pool = mp.Pool()
5 # we ask each process to generate n//mp.cpu_count() points
6 return pool.map(count_inside_point , [n//mp.cpu_count()]∗mp. cpu_count())
7
8 # set seed for each process
9 # first, let’s define a helper function
10 def helper(args):
11 n, s = args
12 seed(s)
13 return count_inside_point(n)
14
15 def generate_points_parallel_set_seed(n):
16 pool = mp.Pool() # we can also specify the number of processes by Pool(number)
17 # we ask each process to generate n//mp.cpu_count() points
18 return pool.map(helper, list(zip([n//mp.cpu_count()]∗mp. cpu_count(),range(mp.cpu_count()))))
19
20 # another optimization via vectorization
21 def generate_points_vectorized(n):
22 p = uniform(−1, 1, size=(n,2))
23 return np.sum(np.linalg.norm(p, axis=1) <= 1)
24
25 def pi_naive(n):
26 print(f’pi: {count_inside_point(n)/n∗4:.6f}’)
27
28 def pi_parallel(n):
29 print(f’pi: {sum(generate_points_parallel_set_seed(n))/n ∗4:.6f}’)
30
31 def pi_vectorized(n):
32 print(f’pi: {generate_points_vectorized(n)/n∗4:.6f}’)

En el fragmento de código de Python anterior, definimos una función generate_points_parallel que devuelve
una lista que contiene los números de puntos internos. Pero el problema con esta función es que cada proceso
en el grupo comparte el mismo estado aleatorio. Como resultado, obtendremos una lista de números
duplicados llamando a esta función. Para solucionar el problema, definimos otra función
generate_points_parallel_set_seed. Veamos los resultados de nuestra estimación de π ejecutándose en una
computadora portátil.
R
1 > source(’pi.R’)
2 > system.time(pi_naive(1e6))
3 naive: pi − 3.144592
4 user system elapsed
5 8.073 1.180 9.333
6 > system.time(pi_parallel(1e6))
7 parallel: pi − 3.1415
8 user system elapsed
9 4.107 0.560 4.833
10 > system.time(pi_vectorized(1e6))
11 vectorized: pi − 3.141224
12 user system elapsed
13 0.180 0.031 0.214

Python
1 >>> import timeit
2 >>> from pi import pi_naive, pi_parallel, pi_vectorized
3 >>> print(f’naive − {timeit.timeit("pi_naive(1000000)",setup=" from __main__ import
pi_naive",number=1):.6f} seconds’)
4 pi: 3.141056
5 naive − 4.363822 seconds
6 >>> print(f’parallel − {timeit.timeit("pi_parallel(1000000)", setup="from __main__ import
pi_parallel",number=1):.6f} seconds’)
7 pi: 3.141032
8 parallel − 2.204697 seconds
9 >>> print(f’vectorized − {timeit.timeit("pi_vectorized(1000000) ",setup="from __main__ import
pi_vectorized",number=1):.6f} seconds’)
10 pi: 3.139936
11 vectorized − 0.148950 seconds

Vemos que el ganador en este ejemplo es la vectorización, y la solución paralela es mejor que la solución
ingenua. Sin embargo, cuando el problema no se puede vectorizar, podemos usar la paralelización para lograr
un mejor rendimiento

I.5 ESTRATEGIA DE EVALUACIÓN


R es un lenguaje de programación perezoso ya que usa evaluación perezosa por defecto [13]. La estrategia de
evaluación perezosa retrasa la evaluación de una expresión hasta que se necesita su valor. Aunque Python es
un lenguaje ávido, es posible simular la evaluación perezosa cero. Cuando se evalúa una expresión en R, en
general sigue un orden de reducción más externo. Pero Python no es un lenguaje de programación perezoso y
la expresión en Python sigue un orden de reducción más interno. Los fragmentos de código a continuación
ilustran la reducción más interna frente a la reducción más externa.

Debido al orden de reducción más externo, el fragmento de código R evalúa primero la potencia de la función
y, dado que si el segundo argumento es 0, no es necesario evaluar el primer argumento. Por lo tanto, la
llamada a la función devuelve 1. Pero el fragmento de código de Python primero evalúa la llamada a la función
de dividir y se genera una excepción debido a la división por cero.
Aunque Python es un lenguaje entusiasta, es posible simular el comportamiento de evaluación perezoso.
Por ejemplo, el código dentro de un generador20 se evalúa cuando se consume el generador, pero no se
evalúa cuando se crea. Podemos crear una secuencia de números de Fibonacci con la ayuda del generador.

Python
1 >>> def fib():
2 ... ’’’
3 ... a generator to create Fibonacci numbers less than 10
4 ... ’’’
5 ... a, b = 0, 1
6 ... while a>> f = fib()
7 ... a, b = b, a+b
8 ... yield a
9 ...
10 >>> f = fib()
11 >>> print(f)
12
13 >>> for e in f: print(e)
14 ...
15 1
16 1
17 2
18 3
19 5
20 8
21 13
22 >>>

En el fragmento de código anterior, creamos un generador que genera la secuencia de números de Fibonacci
menores que 10. Cuando se crea el generador, la secuencia no se genera inmediatamente. Cuando
consumimos el generador en un bucle, cada elemento se genera según sea necesario. También es común
consumir el generador con la siguiente función.

Python
1 >>> f = fib()
2 >>> next(f)
31
4 >>> next(f)
51
6 >>> next(f)
72
8 >>> next(f)
93
10 >>> next(f)
11 5

I.6ACELERAR CON C/C++ EN R/PYTHON


El propósito de esta sección no es enseñar C/C++. Si sabe cómo escribir C/C++, puede ayudar a mejorar el
rendimiento del programa R/Python.
Se recomienda utilizar la técnica de vectorización para acelerar su programa siempre que sea posible. Pero, ¿y
si no es factible vectorizar el algoritmo? Una opción potencial es usar C/ C++ en el código R/Python. Según mi
experiencia limitada, es mucho más fácil usar C/C++ en R con la ayuda del paquete Rcpp que en Python.
En Python, hay más opciones disponibles pero no tan sencillas como la solución de Rcpp. En esta sección,
usemos Cython.
En realidad, Cython en sí mismo es un lenguaje de programación escrito en Python y C. En Rcpp podemos
escribir código C++ directamente y usarlo en código R nativo. Con la ayuda de Cython, podemos escribir código
similar a Python que puede compilarse en C o C++ y envolverse automáticamente en módulos importables de
Python. Cython también podría envolver código C o C++ independiente en módulos importables de python.
Veamos cómo calcular los números de Fibonacci con estas herramientas. El primer paso es instalar el paquete
Rcpp en R y el módulo Cython (use pip) en Python. Una vez que están instalados, podemos escribir el código en
C++ directamente para R. En cuanto a Cython, escribimos el código similar a Python que se compilará en C/C++
más adelante.
Vale la pena señalar que la extensión del archivo Cython anterior es pyx, no py. Y en Fibonacci.pyx, la función
que definimos sigue la sintaxis nativa de Python. Pero en Cython podemos agregar declaraciones de tipeo
estático que a menudo son útiles para mejorar el rendimiento, aunque no es obligatorio. La palabra clave cdef
hace que la función Fibonacci_c sea invisible para Python y, por lo tanto, tenemos que definir la función
Fibonacci_static como un envoltorio que se puede importar a Python. La función Fibonacci no tiene tipo
estático para la evaluación comparativa.
Implementemos también las mismas funciones en R/Python nativo con fines de evaluación comparativa.

Ahora comparemos el rendimiento de diferentes implementaciones.


R
1 > library(microbenchmark)
2 > library(Rcpp)
3 > source(’Fibonacci_native}.R’)
4 > sourceCpp(’Fibonacci.cpp’)
5 > # the native implementation in R
6 > microbenchmark(Fibonacci_native(20), times=1000)
7 Unit: milliseconds
8 expr min lq mean median uq max
9 Fibonacci_native(20) 3.917138 4.056468 4.456824 4.190078 4.462815 26.30846
10 neval
11 1000
12 > the C++ implementation
13 > microbenchmark(Fibonacci(20), times=1000)
14 Unit: microseconds
15 expr min lq mean median uq max neval
16 Fibonacci(20) 14.251 14.482 15.81708 14.582 14.731 775.095 1000

La implementación nativa en R es mucho más lenta que la implementación en C++ (ver la diferencia de
unidades).

Python
1 >>> import pyximport
2 >>> pyximport.install()
3 (None, )
4 >>> from Fibonacci import Fibonacci_static, Fibonacci
5 >>> from Fibonacci_native import Fibonacci_native
6 >>> import timeit
7 >>> print(f’{timeit.timeit("Fibonacci_native(20)",setup="from __main__ import
Fibonacci_native",number=1000):.6f} seconds’ )
8 1.927359 seconds
9 >>> print(f’{timeit.timeit("Fibonacci(20)",setup="from __main__ import Fibonacci",number=1000):.6f}
seconds’)
10 0.316726 seconds
11 >>> print(f’{timeit.timeit("Fibonacci_static(20)",setup="from __main__ import
Fibonacci_static",number=1000):.6f} seconds’ )
12 0.012741 seconds

Los resultados muestran que la implementación con tipo estático es la más rápida y la implementación nativa
en Python puro es la más lenta. De nuevo el tiempo medido por timeit. timeit es el tiempo total para ejecutar
la instrucción principal 1000 veces. El tiempo promedio por ejecución de la función Fibonacci_static está cerca
del tiempo promedio por ejecución de Fibonacci en R.

I.7 UNA PRIMERA IMPRESIÓN DE LA PROGRAMACIÓN FUNCIONAL


Los lenguajes funcionales (de programación) definen los programas como funciones matemáticas y los tratan
como de primera clase22. En la programación funcional pura, una función toma la entrada y devuelve la salida
sin tener ningún efecto secundario. Un efecto secundario significa que se modifica una variable de estado fuera
de la función. Por ejemplo, imprimir el valor de una variable dentro de una función es un efecto secundario.
Tanto R como Python no son lenguajes puramente funcionales per se, aunque R tiene más funciones que
Python [5]. Si te preguntas por qué la función de retorno podría ignorarse para devolver un valor en una
función R, ahora tiene la explicación: una función debería devolver la salida automáticamente desde una
perspectiva de FP.
No creo que tenga ningún sentido debatir si deberíamos elegir OOP o FP usando R/Python. Y ambos lenguajes
admiten múltiples paradigmas de programación (FP, OOP, etc.). Si desea hacer FP purista, ni R ni Python serían
la mejor opción. Por ejemplo, el FP purista debe evitar el bucle porque el bucle siempre implica un contador de
iteraciones cuyo valor cambia con las iteraciones, lo que obviamente es un efecto secundario. Por lo tanto, la
recursividad es muy importante en FP. Pero la optimización de recurrencia de cola no es compatible con R y
Python, aunque se puede implementar a través de trampoline23.
Presentemos algunas herramientas que podrían ayudar a obtener una primera impresión de FP en R y Python.

2.8.1 Función anónima


Hemos visto funciones anónimas antes. Las funciones anónimas no tienen nombres. Se utilizan comúnmente
en FP. Para recapitular, veamos cómo definir una función anónima inútil.

Por lo general, definimos funciones anónimas porque queremos pasarlas a otras funciones. Y así, las funciones
definidas anteriormente son inútiles ya que no se pasan a ninguna otra función y después de la definición se
eliminan automáticamente de la memoria.
Por supuesto, podemos asignar nombres a las funciones anónimas, pero ¿cuál es el punto de nombrar
funciones anónimas?

2.8.2 Mapa
El mapa podría usarse para evitar bucles. El primer argumento para mapear es una función.
En R, usamos Map , que se envía con la base R; y en Python usamos el mapa que es también una función
incorporada.
Hay algunas cosas a tener en cuenta de los fragmentos de código anteriores. Primero, el valor devuelto por
Map en R es una lista en lugar de un vector. Esto se debe a que Map es solo un contenedor de la función
mapply en R, y el argumento SIMPLIFY se establece en FALSE. Si queremos obtener un valor de vector devuelto,
simplemente use mapply en su lugar.
R
1 > mapply(function(x, y) x∗y, c(1:3), c(4:6), SIMPLIFY=TRUE)
2 [1] 4 10 18

El valor devuelto del mapa en Python es un objeto de mapa que es un iterador. La función generadora que
hemos visto antes también es un iterador. Para usar un iterador, podemos convertirlo en una lista, u obtener el
siguiente valor usando la siguiente función, o simplemente usar un bucle for.

Python
1 >>> # use next on an iterator
2 >>> o=map(lambda x, y: x∗y, [1,2,3], [4,5,6])
3 >>> next(o)
44
5 >>> next(o)
6 10
7 >>> o=map(lambda x, y: x∗y, [1,2,3], [4,5,6])
8 >>> # use for loop on an iterator
9 >>> for e in o:print(e)
10 ...
11 4
12 10
13 18
14 >>> # convert an iterator to a list
15 >>> o=map(lambda x, y: x∗y, [1,2,3], [4,5,6])
16 >>> list(o)
17 [4, 10, 18]

2.8.3 Filtro
Similar a map, el primer argumento de Filter en R y filter en Python también es una función. Esta función se
utiliza para obtener los elementos que cumplen una determinada condición especificada en el primer
argumento. Por ejemplo, podemos usarlo para obtener los números pares.

2.8.4 Reducir
18] Reducir se comporta de manera un poco diferente a Map y Filter. El primer argumento es una función con
exactamente dos argumentos. El segundo argumento es una secuencia, en cada elemento del cual se aplicará
el primer argumento de función de izquierda a derecha. Y hay un argumento opcional llamado inicializador.
Cuando se proporciona el inicializador, se utilizará como primer argumento del argumento de función de
Reduce. Los ejemplos a continuación muestran cómo funciona

Tenga en cuenta que para usar reduce en Python, debemos importarlo desde el módulo functools.
I.8 VARIOS
Hemos introducido los conceptos básicos de la programación R/Python hasta ahora. Hay mucho más que
aprender para convertirse en un usuario avanzado de R/Python. Por ejemplo, los usos apropiados de iterador,
generador y decorador podrían mejorar tanto la concisión como la legibilidad de su código Python. El
generador24 se ve comúnmente en programas de aprendizaje automático para preparar muestras de
entrenamiento/prueba. decorador es una especie de azúcar sintáctico que permite modificar el
comportamiento de una función de forma sencilla. En R no hay un iterador, un generador o un decorador
incorporados, pero puede encontrar algunas bibliotecas de terceros para imitar estas características; o puede
intentar implementar el suyo propio.
Una ventaja de Python sobre R es que hay algunos módulos integrados que contienen estructuras de datos de
alto rendimiento o algoritmos de uso común implementados de manera eficiente. Por ejemplo, disfruto usando
la estructura deque en el módulo de colecciones de Python25 , pero no hay una contraparte integrada en R.
Hemos escrito nuestro propio algoritmo de búsqueda binaria anteriormente en este capítulo, que también
puede ser reemplazado por las funciones en el módulo integrado. -en el módulo bisect26 en Python.
Otro aspecto importante de la programación es la prueba. La prueba unitaria es un método típico de prueba de
software que se adopta comúnmente en la práctica. En R hay dos paquetes de terceros testthat y RUnit. En
Python, la prueba unitaria incorporada es bastante poderosa. Además, el módulo de terceros pytest27 es muy
popular.
tabla de datos y pandas

Al recibir comentarios y solicitudes de los lectores sobre los primeros capítulos, decidí dedicar un capítulo
entero a la introducción de data .table y pandas. Por supuesto, hay muchas otras excelentes herramientas de
análisis de datos tanto en R como en Python. Por ejemplo, a muchos usuarios de R les gusta usar dplyr para
crear canalizaciones de análisis de datos. El rendimiento de data.table es superior y esa es la razón principal por
la que siento que no hay necesidad de usar otras herramientas para las mismas tareas en R. Pero si eres un
gran admirador del operador de tuberías %>%, puedes usar datos . mesa y dplyr juntos. En cuanto al
ecosistema de big data, Apache Spark tiene API tanto en R como en Python. Recientemente, también hay
algunos proyectos emergentes que apuntan a una mejor usabilidad y rendimiento, como Apache Arrow.

3.1 SQL
Al igual que en los capítulos anteriores, presentaré las herramientas una al lado de la otra. Sin embargo, siento
que antes de sumergirme en el mundo de data.table y pandas, es mejor hablar poco de sistema de SQL . SQL es
un lenguaje de consulta diseñado para administrar datos en un de administración de base de datos relacional
(RDBMS). Algunos de los RDBMS más populares incluyen MS SQL Server, MySQL, PostgreSQL, etc. Diferentes
RDBMS pueden usar lenguajes SQL con diferencias importantes o sutiles.

Si nunca ha usado RDBMS, puede preguntarse por qué lo necesitamos.


• primero, necesitamos un sistema para almacenar los datos;
• segundo, también necesitamos un sistema que nos permita acceder, administrar y actualizar los datos.
En este capítulo, utilizaremos el conjunto de datos público mtcars como ejemplo. Este conjunto de datos
estaba disponible en R4 y originalmente se informó en [11].
Supongamos que hay una tabla mtcars en una base de datos (estoy usando sqlite3 en este libro) y veamos
algunas tareas simples que podemos hacer con consultas SQL.
Tabla 3.1: Datos parciales de mtcars cargados desde R 3.5.1

SQL
1 sqlite> select ∗ from mtcars limit 2;
2 name,mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
3 "Mazda RX4" ,21,6,160,110,3.9,2.62,16.46,0,1,4,4
4 "Mazda RX4 Wag" ,21,6,160,110,3.9,2.875,17.02,0,1,4,4

En el ejemplo anterior, selecciono dos filas de la tabla usando la sintaxis select from. El límite de palabras clave
en sqlite especifica el número de filas a devolver. En otros RDBMS, es posible que necesitemos usar top en su
lugar. Es sencillo seleccionar condiciones con la palabra clave where .
SQL
1 sqlite> select mpg,cyl from mtcars where name = ’Mazda RX4 Wag’;
2 mpg,cyl
3 21,6

Definitivamente podemos usar más condiciones con la cláusula where .


SQL
1 sqlite> .mode column −− make the output aligned; and yes we use ’−−’ to start comment in many SQL
languages
2 sqlite> select name, mpg, cyl,vs,am from mtcars where vs=1 and am=1;
3 name mpg cyl vs am
4 −−−−−−−−−− −− −−−−−− −− − −−−− −−−−−
5 Datsun 710 22.8 4 1 1
6 Fiat 128 32.4 4 1 1
7 Honda Civi 30.4 4 1 1
8 Toyota Cor 33.9 4 1 1
9 Fiat X1−9 27.3 4 1 1
10 Lotus Euro 30.4 4 1 1

11 Volvo 142E 21.4 4 1 1


Solo estamos accediendo a filas y columnas específicas de la tabla en la base de datos con seleccionar desde
dónde. También podemos hacer algo un poco más elaborado, por ejemplo, para obtener el máximo, el mínimo
y el promedio de mpg para todos los vehículos agrupados por el número de cilindros

SQL
1 sqlite> select cyl, max(mpg) as max, min(mpg) as min, avg(mpg) as avg from mtcars group by cyl order by cyl;
2 cyl max min avg
3 −−−−−−−−−− −−−−−−−−−− −−−−−−−−−− −−−
44 33.9 21.4 26.6636363636364
56 21.4 17.8 19.7428571428571
68 19.2 10.4 15.1

En el ejemplo anterior, hay algunas cosas que vale la pena señalar. Usamos as para crear un alias para una
variable; agrupamos las filas originales por el número de cilindros con la palabra clave group by; y ordenamos
las filas de salida con la palabra clave order by. max, min y avg son funciones integradas que podemos usar
directamente.
También es posible tener funciones definidas por el usuario en SQL como lo hacemos habitualmente en otros
lenguajes de programació
Las consultas SQL pueden ser muy complejas y largas. Por lo general, una consulta larga puede contener
subconsultas o Expresiones de tabla comunes (CTE). Primero, veamos un ejemplo de subconsulta para obtener
el mpg promedio para automóviles con 6 cilindros como se muestra a continuación.
SQL
1 sqlite> select am, avg(mpg) avg_mpg from (select ∗ from mtcars where cyl==6) group by am;
2 am avg_mpg
3 −−−−−−−−−− −−−−−−−−−−
40 19.125
51 20.5666666
La subconsulta entre paréntesis especifica el hecho de que solo estamos interesados en las filas con cyl==6. CTE
podría obtener los mismos resultados que se muestran a continuación
SQL
1 sqlite> with temp_table as (select ∗ from mtcars where cyl==6) select am, avg(mpg) avg_mpg from
temp_table group by am;
2 am avg_mpg
3 −−−−−−−−−− −−−−−−−−−−
40 19.125
51 20.5666666
Básicamente, un CTE se puede considerar como un conjunto de resultados temporal al que se puede hacer
referencia varias veces en una consulta. Se pueden utilizar varios CTE en la misma consulta. El uso de CTE
mejora la legibilidad de la consulta con un rendimiento similar al de la subconsulta en consultas complejas. En
algunos casos, CTE podría ser un cuello de botella en el rendimiento y puede considerar usar una tabla
temporal que no se tratará en este capítulo.

3.2 COMENZAR CON DATA.TABLE Y PANDAS


SQL es una herramienta muy poderosa para el análisis de datos, pero funciona en RDBMS y, en general, no
podemos aplicar las funciones de R o Python directamente a las tablas de la base de datos. Muchos
profesionales de la ciencia de datos tienen que trabajar con bases de datos a veces, pero con mayor frecuencia
necesitan trabajar en lenguajes de programación como R o Python. Hemos introducido data.frame tanto en R
como en Python en capítulos anteriores. Un marco de datos es como una tabla de base de datos que puede
operar dentro del idioma correspondiente. Por lo general, un marco de datos se almacena en la memoria, pero,
por supuesto, también se puede deserializar para almacenarlo en discos duros. Con los objetos similares a
data.frame, podríamos construir una mejor tubería de procesamiento de datos leyendo solo los datos
originales de la base de datos y almacenando el resultado final en la base de datos si es necesario. La mayoría
de los trabajos que podemos hacer con objetos similares a data.frame también se pueden hacer en RDBMS con
SQL. Pero pueden requerir interacciones intensas con una base de datos, lo cual no es lo mejor si se puede
evitar.
Ahora, comencemos con data.table y pandas. En este libro, usaré datos. tabla 1.12.0 y pandas 0.24.0.

Guardé los datos de mtcars en un archivo csv5 (code/chapter3/mtcars.csv), que se carga desde R 3.5.1. Aunque
los datos de mtcars se cargan en el entorno R de forma predeterminada, carguemos los datos leyendo el
archivo csv sin procesar con fines de aprendizaje.

R
1 mtcars_dt=fread(’mtcars.csv’)
2 head(mtcars_dt)
3 name mpg cyl disp hp drat wt qsec vs am gear carb
4 1: Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
5 2: Mazda RX4 Wag 21.0
6 160 110 3.90 2.875 17.02 0 1 4 4 6 3: Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
7 4: Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 8 5: Hornet Sportabout 18.7
8 360 175 3.15 3.440 17.02 0 0 3 2
9 6: Valiant 18.1 6 225105 2.76 3.460 20.22 1 0 3 1
Python
1 >>> import pandas as pd
2 >>> mtcars_df=pd.read_csv(’mtcars.csv’)
3 >>> mtcars_df.head(5)
4 name mpg cyl disp hp ... qsec vs am gear carb
5 0 Mazda RX4 21.0
6 160.0 110 ... 16.46 0 1 4 4 6 1 Mazda RX4 Wag 21.0 6 160.0 110 ... 17.02 0 1 4 4
7 2 Datsun 710 22.8 4 108.0 93 ... 18.61 1 1 4 1
8 3 Hornet 4 Drive 21.4 6 258.0 110 ... 19.44 1 0 3 1
9 4 Hornet Sportabout 18.7 8 360.0 175 ... 17.02 0 0 3 2
10

11 [5 rows x 12 columns]

El tipo de mtcars_dt es data.table, no data.frame. Aquí usamos la función fread de data.table para leer un
archivo y el tipo de salida es data.table directamente.

Con respecto a la lectura de csv en R, un muy buen paquete es readr para archivos muy grandes, pero la salida
tiene un tipo data.frame . En la práctica, es muy común convertir un data.frame a data.table con la función
as.data.table.
3.3 INDEXACIÓN Y SELECCIÓN DE DATOS

Antes de introducir las reglas de indexación en data.table y pandas, es mejor entender la clave en data.table y
el índice en pandas.

¿Cuál es la clave en una tabla de datos? Hemos hablado de RDBMS y SQL en la sección anterior. Con select
desde donde podemos acceder fácilmente a filas específicas que cumplan ciertas condiciones. Cuando la tabla
de la base de datos es demasiado grande, se utiliza un índice de base de datos para mejorar el rendimiento de
las operaciones de recuperación de datos. Esencialmente, un índice de base de datos es una estructura de
datos y, para mantener la estructura de datos, es posible que se requiera un costo adicional (por ejemplo,
espacio). La razón para usar la clave en data.table y el índice en pandas es muy similar. Ahora veamos cómo
configurar la clave y el índice.

R
1> setkey(mtcars_dt,name)
2> > key(mtcars_dt)
3> [1] "name"
4> > head(mtcars_dt ,5)
5> name mpg cyl disp hp drat wt qsec vs am gear carb
6> 1: AMC Javelin 15.2 8 304 150 3.15 3.435 17.30 0 0
32
7> 2: Cadillac Fleetwood 10.4 8 472 205 2.93 5.250 17.98 0 0
34
8> 3: Camaro Z28 13.3 8 350 245 3.73 3.840 15.41 0 0
34
9> 4: Chrysler Imperial 14.7 8 440 230 3.23 5.345 17.42 0 0
34
10> 5: Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1
41

Python
1 1 >>> mtcars_df.set_index(’name’,inplace=True)
2 >>> mtcars_df.index.name
3 ’name’
4 >>> mtcars_df.head(5)
5 mpg cyl disp hp drat ... qsec vs am gear carb
6 name ...
7 Mazda RX4 21.0 6 160.0 110 3.90 ... 16.46 0
144
8 Mazda RX4 Wag 21.0 6 160.0 110 3.90 ... 17.02 0
144
9 Datsun 710 22.8 4 108.0 93 3.85 ... 18.61 1
141
10 Hornet 4 Drive 21.4 6 258.0 110 3.08 ... 19.44 1
031
11 Hornet Sportabout 18.7 8 360.0 175 3.15 ... 17.02 0
032
Hay bastantes cosas que vale la pena señalar de los fragmentos de código anteriores. Cuando usamos la
función setkey, las comillas para el nombre de la columna son opcionales. Entonces setkey( mtcars_dt,
nombre) es equivalente a setkey(mtcars_dt, 'nombre'). Pero en pandas, se requieren comillas. El efecto de
setkey está en su lugar, lo que significa que no se hacen copias de los datos en absoluto. Pero en pandas, por
defecto, set_index establece el índice en una copia de los datos y se devuelve la copia modificada. Por lo tanto,
para lograr el efecto en su lugar, hemos establecido explícitamente el argumento inplace=True . Otra diferencia
es que setkey ordenaría los datos originales en su lugar automáticamente, pero set_index no lo hace. También
vale la pena señalar que cada pandas data.frame tiene un índice; y por defecto es numpy.arange(n) donde n es
el número de filas. Pero no hay una clave predeterminada en una tabla de datos.
En el ejemplo anterior, solo usamos una sola columna como clave/índice. También es posible utilizar varias
columnas.

Para usar varias columnas como clave en data.table, usamos la función setkeyv. También es interesante que
tengamos que usar index.names en lugar de index.name para obtener los nombres de múltiples columnas del
índice (que se llama MultiIndex) en pandas. Hay combinaciones duplicadas de (cyl, gear) en los datos, lo que
implica que la clave o el índice podrían estar duplicados.
Una vez que se establece la clave/índice, podemos acceder rápidamente a las filas con índices dados.
R
1 > mtcars_dt[’Merc 230’]
2 name mpg cyl disp hp drat wt qsec vs am gear carb
3 1: Merc 230 22.8
4 140.8 95 3.92 3.15 22.9 1 0 4 2 4 > mtcars_dt[c(’Merc 230’,’Camaro Z28’)] # multiple values of a single
index
5 name mpg cyl disp hp drat wt qsec vs am gear carb
6 1: Merc 230 22.8 4 140.8 95 3.92 3.15 22.90 1 0 4 2
7 2: Camaro Z28 13.3 8 350.0 245 3.73 3.84 15.41 0 0 3 4
8 > setkeyv(mtcars_dt,c(’cyl’,’gear’))
9 > mtcars_dt[.(6,4)] # work with key vector using .()
10 name mpg cyl disp hp drat wt qsec vs am gear carb
11 1: Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4
12 2: Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4
13 3: Merc 280 19.2 6 167.6 123 3.92 3.440 18.30 1 0 4 4
14 4: Merc 280C 17.8 6 167.6 123 3.92 3.440 18.90 1 0 4 4
15 > mtcars_dt[.(c(6,8),c(4,3))] # key vector with multiple values
16 name mpg cyl disp hp drat wt qsec vs am gear carb
17 1: Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0
144
18 2: Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0
144
19 3: Merc 280 19.2 6 167.6 123 3.92 3.440 18.30 1
044
20 4: Merc 280C 17.8 6 167.6 123 3.92 3.440 18.90 1
044
21 5: AMC Javelin 15.2 8 304.0 150 3.15 3.435 17.30 0
032
22 6: Cadillac Fleetwood 10.4 8 472.0 205 2.93 5.250 17.98
0034
23 7: Camaro Z28 13.3 8 350.0 245 3.73 3.840 15.41 0 0 3 4
24 8: Chrysler Imperial 14.7 8 440.0 230 3.23 5.345 17.42 0
034
25 9: Dodge Challenger 15.5 8 318.0 150 2.76 3.520 16.87 0
032
26 10: Duster 360 14.3 8 360.0 245 3.21 3.570 15.84 0 0
342
27 11: Hornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0
032
28 12: Lincoln Continental 10.4 8 460.0 215 3.00 5.424 17.82 0
034
29 13: Merc 450SE 16.4 8 275.8 180 3.07 4.070 17.40 0
033
30 14: Merc 450SL 17.3 8 275.8 180 3.07 3.730 17.60 0
033
31 15: Merc 450SLC 15.2 8 275.8 180 3.07 3.780 18.00 0
033
32 16: Pontiac Firebird 19.2 8 400.0 175 3.08 3.845 17.05 0
032
Aquí hay un poco de explicación para el código anterior. Simplemente podemos usar [] para acceder a las filas
con los valores clave especificados si la clave tiene un tipo de carácter. Pero si la clave tiene un tipo numérico,
se requiere list() para encerrar los valores de la clave. En data.table, .() es solo un alias de list(), lo que significa
que obtendríamos los mismos resultados con mtcars_dt[list (6,4)]. Por supuesto, también podemos usar do
mtcars_dt[.('Merc 230')] que es equivalente a mtcars_dt[.('Merc 230')].

Python

1 >>> mtcars_df.loc[’Merc 230’]


2 mpg 22.80
3 cyl 4.00
4 disp 140.80
5 hp 95.00
6 drat 3.92
7 wt 3.15
8 qsec 22.90
9 vs 1.00
10 am 0.00
11 gear 4.00
12 carb 2.00
13 Name: Merc 230, dtype: float64
14 >>> mtcars_df.loc[[’Merc 230’,’Camaro Z28’]] # multiple values of a single index
15 mpg cyl disp hp drat wt qsec vs am gear carb
16 name
17 Merc 230 22.8 4 140.8 95 3.92 3.15 22.90 1 0 4 2
18 Camaro Z28 13.3 8 350.0 245 3.73 3.84 15.41 0 0 3 4
19
20 mtcars_df.set_index([’cyl’,’gear’],inplace=True)
21 >>> mtcars_df.loc[(6,4)] # work with MultiIndex using ()
22 mpg disp hp drat wt qsec vs am carb
23 cyl gear
24 6 4 21.0 160.0 110 3.90 2.620 16.46 0 1 4
25 4 21.0 160.0 110 3.90 2.875 17.02 0 1 4
26 4 19.2 167.6 123 3.92 3.440 18.30 1 0 4
27 4 17.8 167.6 123 3.92 3.440 18.90 1 0 4
28 >>> # you may notice that the name column disappeared; that would be explained later
29 >>> mtcars_df.loc[[(6,4) ,(8,3)]] # MultiIndex with multiple values
30 mpg disp hp drat wt qsec vs am carb
31 cyl gear
32 6 4 21.0 160.0 110 3.90 2.620 16.46 0 1 4
33 4 21.0 160.0 110 3.90 2.875 17.02 0 1 4
34
35 4 19.2 167.6 123 3.92 3.440 18.30 1 0 4
36 4 17.8 167.6 123 3.92 3.440 18.90 1 0 4
37 8 3 18.7 360.0 175 3.15 3.440 17.02 0 0 2
38 3 14.3 360.0 245 3.21 3.570 15.84 0 0 4
39 3 16.4 275.8 180 3.07 4.070 17.40 0 0 3
40 3 17.3 275.8 180 3.07 3.730 17.60 0 0 3
41 3 15.2 275.8 180 3.07 3.780 18.00 0 0 3
42 3 10.4 472.0 205 2.93 5.250 17.98 0 0 4
43 3 10.4 460.0 215 3.00 5.424 17.82 0 0 4
44 3 14.7 440.0 230 3.23 5.345 17.42 0 0 4
45 3 15.5 318.0 150 2.76 3.520 16.87 0 0 2
46 3 15.2 304.0 150 3.15 3.435 17.30 0 0 2
47 3 13.3 350.0 245 3.73 3.840 15.41 0 0 4
48 3 19.2 400.0 175 3.08 3.845 17.05 0 0 2

En comparación con data.table, necesitamos usar el método loc al acceder a las filas según el índice. El método
loc también acepta condiciones booleanas.

Python

1 >>> mtcars_df.loc[mtcars_df.mpg >30] # select the vehicles with mpg>30

2 mpg disp hp drat wt qsec vs am carb

3 cyl gear

4 4 4 32.4 78.7 66 4.08 2.200 19.47 1 1 1

5 4 30.4 75.7 52 4.93 1.615 18.52 1 1 2

6 4 33.9 71.1 65 4.22 1.835 19.90 1 1 1

7 5 30.4 95.1 113 3.77 1.513 16.90 1 1 2

Cuando se usan condiciones booleanas, loc podría ignorarse por convenienc

Python

1 >>> mtcars_df[mtcars_df.mpg >30] # ignore loc with boolean conditions

2 mpg disp hp drat wt qsec vs am carb

3 cyl gear

4 4 4 32.4 78.7 66 4.08 2.200 19.47 1 1 1

5 4 30.4 75.7 52 4.93 1.615 18.52 1 1 2

6 4 33.9 71.1 65 4.22 1.835 19.90 1 1 1

7 5 30.4 95.1 113 3.77 1.513 16.90 1 1 2


Si no se necesita la clave/índice, podemos eliminar la clave o restablecer el índice. Para data.table podemos
establecer una nueva clave para anular la existente que luego se convierte en una columna. Pero en pandas, el
método set_index elimina el índice existente que también desaparece del data.frame.

R
1 > key(mtcars_dt)

2 [1] "cyl" "gear"

3 > setkey(mtcars_dt, NULL) # remove the existing key

4 > key(mtcars_dt)

5 NULL

6 > setkey(mtcars_dt, ’gear’)

7 > key(mtcars_dt)

8 [1] "gear"

9 > setkey(mtcars_dt, ’name’) # override the existing key

10 > key(mtcars_dt)

11 [1] "name"

Python
1 >>> mtcars_df.index.names
2 FrozenList([’cyl’, ’gear’])
3 >>> mtcars_df.reset_index(inplace=True) # remove the existing index
4 >>> mtcars_df.index.names
5 FrozenList([None])
6 >>> mtcars_df.set_index([’gear’], inplace=True)
7 >>> mtcars_df.index.name
8 ’gear’
9 >>> mtcars_df.columns # list all columns
10 Index([’cyl’, ’name’, ’mpg’, ’disp’, ’hp’, ’drat’, ’wt’, ’qsec’, ’vs’, ’am’,
11 ’carb’],
12 dtype=’object’)
13 >>> mtcars_df.set_index(’name’, inplace=True)
14 >>> mtcars_df.columns # the name column disappears
15 Index([’cyl’, ’mpg’, ’disp’, ’hp’, ’drat’, ’wt’, ’qsec’, ’vs’, ’ am’, ’carb’], dtype=’object’)
16 >>>
En el Capítulo 1, presentamos la indexación basada en enteros para lista/vector. También es aplicable al marco
de datos y data.table.
R
1 > mtcars_dt=fread(’mtcars.csv’)
2 > mtcars_dt[c(1,2,6),]
3 name mpg cyl disp hp drat wt qsec vs am gear carb
4 1: Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
5 2: Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
6 3: Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
Python
1 >>> mtcars_df=pd.read_csv(’mtcars.csv’)
2 >>> mtcars_df.iloc[[0,1,5]] # again, indices in python are zero− based.
3 name mpg cyl disp hp ... qsec vs am gear carb
4 0 Mazda RX4 21.0 6 160.0 110 ... 16.46 0 1 4 4
5 1 Mazda RX4 Wag 21.0 6 160.0 110 ... 17.02 0 1 4 4 6
5 Valiant 18.1 6 225.0 105 ... 20.22 1 0 3 1
Hasta ahora hemos visto cómo acceder a filas específicas. ¿Qué pasa con las columnas? Acceder a columnas en
data.table y pandas es bastante sencillo. Para data.table, podemos usar el signo $ para acceder a una sola
columna o un vector para especificar varias columnas dentro de []. Para data.frame en pandas, podemos usar .
para acceder a una sola columna o una lista para especificar múltiples columnas dentro de [].
Además de pasar un vector para acceder a múltiples columnas en data.table, también podemos usar
(variable_1,variable_2,...)
R
1 > mtcars_dt[1:5,.(mpg,cyl,hp)] # without quotes for variables
2 mpg cyl hp
3 1: 21.5 4 97
4 2: 22.8 4 93
5 3: 24.4 4 62
6 4: 22.8 4 95
7 5: 32.4 4 66

También es posible realizar divisiones de columnas basadas en enteros.

Para acceder a filas y columnas específicas, existen dos estrategias:


1) seleccione filas y luego seleccione columnas en una cadena;
2) seleccionar filas y columnas simultáneamente.

Veamos algunos ejemplos.


R
1 > mtcars_dt=fread(’mtcars.csv’)
2 > setkey(mtcars_dt,’gear’)
3 > mtcars_dt[.(6),c(’mpg’,’cyl’,’hp’)] # use strategy 2;
4 mpg cyl hp
5 1: 21.4 6 110
6 2: 18.1 6 105
7 3: 21.0 6 110
8 4: 21.0 6 110
9 5: 19.2 6 123
10 6: 17.8 6 123
11 7: 19.7 6 175
12 > mtcars_dt[.(6)][,c(’mpg’,’cyl’,’hp’)] # use strategy 1;
13 mpg cyl hp
14 1: 21.4 6 110
15 2: 18.1 6 105
16 3: 21.0 6 110
17 4: 21.0 6 110
18 5: 19.2 6 123
19 6: 17.8 6 123
20 7: 19.7 6 175

Python
1 >>> mtcars_df=pd.read_csv(’mtcars.csv’)
2 >>> mtcars_df.set_index(’cyl’, inplace=True)
3 >>> mtcars_df.loc[6,[’mpg’,’hp’]] # use strategy 2; we can’t list the index as a normal column; while a key in
data.table is still a normal column
4 mpg hp
5 cyl
6 6 21.0 110
7 6 21.0 110
8 6 21.4 110
9 6 18.1 105
10 6 19.2 123
11 6 17.8 123
12 6 19.7 175
13 >>> mtcars_df.loc[6][[’mpg’,’hp’]] # us strategy 1
14 mpg hp
15 cyl
16 6 21.0 110
17 6 21.0 110
18 6 21.4 110
19 6 18.1 105
20 6 19.2 123
21 6 17.8 123
22 6 19.7 175

Como hemos visto, usar la función setkey para data.table ordena la data.table automáticamente. Sin embargo,
no siempre se desea clasificar. En data.table, hay otra función setindex/setindexv que tiene efectos similares a
setkey/setkeyv pero no ordena data.table. Además, una tabla de datos podría tener varios índices, pero no
puede tener varias claves.
R
1 > setindex(mtcars_dt,’cyl’)
2 > head(mtcars_dt ,5) # not sorted by the index
3 name mpg cyl disp hp drat wt qsec vs am gear carb
4 1: Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
5 2: Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
6 3: Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
7 4: Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
8 5: Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
9 > mtcars_dt[.(6), on=’cyl’] # we use on to specify the index
10 name mpg cyl disp hp drat wt qsec vs am gear carb
11 1: Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4
12 2: Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4
13 3: Hornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1
14 4: Valiant 18.1 6 225.0 105 2.76 3.460 20.22 1 0 3 1
15 5: Merc 280 19.2 6 167.6 123 3.92 3.440 18.30 1 0 4 4
16 6: Merc 280C 17.8 6 167.6 123 3.92 3.440 18.90 1 0 4 4
17 7: Ferrari Dino 19.7 6 145.0 175 3.62 2.770 15.50 0 1 5 6
18 > setindexv(mtcars_dt,c(’cyl’,’gear’))
19 > mtcars_dt[.(6,3),on=c(’cyl’,’gear’)]
20 name mpg cyl disp hp drat wt qsec vs am gear carb
21 1: Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
22 2: Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
23
24 > mtcars_dt[.(4),on=’cyl’] # the index ’cyl’ still works after set c(’cyl’,’gear’) as indexv
25 name mpg cyl disp hp drat wt qsec vs am gear carb
26 1: Datsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1
27 2: Merc 240D 24.4 4 146.7 62 3.69 3.190 20.00 1 0 4 2
28 3: Merc 230 22.8 4 140.8 95 3.92 3.150 22.90 1 0 4 2
29 4: Fiat 128 32.4 4 78.7 66 4.08 2.200 19.47 1 1 4 1
30 5: Honda Civic 30.4 4 75.7 52 4.93 1.615 18.52 1 1 4 2
31 6: Toyota Corolla 33.9 4 71.1 65 4.22 1.835 19.90 1 1 4 1
32 7: Toyota Corona 21.5 4 120.1 97 3.70 2.465 20.01 1 0 3 1
33 8: Fiat X1−9 27.3 4 79.0 66 4.08 1.935 18.90 1 1 4 1
34 9: Porsche 914−2 26.0 4 120.3 91 4.43 2.140 16.70 0 1 5 2
35 10: Lotus Europa 30.4 4 95.1 113 3.77 1.513 16.90 1 1 5 2
36 11: Volvo 142E 21.4 4 121.0 109 4.11 2.780 18.60 1 1 4 2

3.4 AGREGAR/ELIMINAR/ACTUALIZAR
Primero, veamos cómo eliminar una sola columna.
El operador:= en data.table se puede usar para agregar/eliminar/actualizar columnas, por referencia. Por lo
tanto, cuando usamos:= no se crean copias de los datos. Familiarizarse con este operador es fundamental para
dominar data.table. A continuación, veamos cómo eliminar varias columnas al mismo tiempo.

El hecho interesante del código anterior es que en R la función %in% está vectorizada, pero la función in en
Python no lo es
Agregar una sola columna a una tabla de datos o DataFrame existente es tan sencillo como eliminarla
R
1 > mtcars_dt$new_col=1 # method 1
2 > head(mtcars_dt, 2)
3 name mpg disp drat wt qsec vs am gear carb new_col
4 1: Mazda RX4 21.0 160 3.90 2.620 16.46 0 1 4 4 1
5 2: Mazda RX4 Wag 21.0 160 3.90 2.875 17.02 0 1 4 4 1
6 > mtcars_dt$new_col=NULL
7 > mtcars_dt[, new_col:=1] # method 2
8 > head(mtcars_dt, 2)
9 name mpg disp drat wt qsec vs am gear carb new_col
10 1: Mazda RX4 21 160 3.9 2.620 16.46 0 1 4 4 1
11 2: Mazda RX4 Wag 21 160 3.9 2.875 17.02 0 1 4 4 1
Python
1 >>> mtcars_df[’new_col’]=1
2 >>> mtcars_df.head(2)
3 name mpg disp drat wt qsec vs am gear carb new_col
4 0 Mazda RX4 21.0 160.0 3.9 2.620 16.46 0 1 4 4 1
5 1 Mazda RX4 Wag 21.0 160.0 3.9 2.875 17.02 0 1 4 4 1

Agregar varias columnas es un poco complicado en comparación con agregar una sola columna.
R
1 > mtcars_dt=fread(’mtcars.csv’) 2 > mtcars_dt[, ‘:=‘(nc1=1,nc2=2)] 3 > head(mtcars_dt, 2) 4 name mpg cyl
disp hp drat wt qsec vs am gear carb nc1 nc2 5 1: Mazda RX4 21 6 160 110 3.9 2.620 16.46 0 1 4 4 1 2 6 2:
Mazda RX4 Wag 21 6 160 110 3.9 2.875 17.02 0 1 4 4 1 2
Python
1 >>> mtcars_df=mtcars_df.assign(∗∗{’nc1’:1, ’nc2’:2}) 2 >>> mtcars_df.head(2) 3 name mpg cyl disp hp ... am
gear carb nc1 nc2 4 0 Mazda RX4 21.0 6 160.0 110 ... 1 4 4 1 2 5 1 Mazda RX4 Wag 21.0 6 160.0 110 ... 1 4 4 1 2
6 7 [2 rows x 14 columns]
En el código R, usamos ':=' para crear varias columnas. En el código de Python, colocamos las nuevas columnas
en un diccionario y usamos la función de asignación con el operador de desempaquetado del diccionario ∗∗.
Para conocer el operador de desempaquetado del diccionario, consulte el documento oficial6. El método de
asignación de un DataFrame no tiene un argumento en el lugar, por lo que debemos asignar el DataFrame
modificado al original de forma explícita.
Ahora veamos cómo actualizar los valores. Podemos actualizar toda la columna o solo la columna en filas
específicas.
R
1 > mtcars_dt=fread(’mtcars.csv’) 2 > mtcars_dt[, ‘:=‘(nc1=1, nc2=2)] 3 > mtcars_dt[, nc1:=10] # update the
entire column c1 4 > head(mtcars_dt ,2) 5 name mpg cyl disp hp drat wt qsec vs am gear carb nc1 nc2 6 1:
Mazda RX4 21 6 160 110 3.9 2.620 16.46 0 1 4 4 10 2 7 2: Mazda RX4 Wag 21 6 160 110 3.9 2.875 17.02 0 1 4 4
10 2 8 > mtcars_dt[cyl==6, nc1:=3] # update the nc1 for rows with cyl=6 9 > head(mtcars_dt ,2) 10 name mpg
cyl disp hp drat wt qsec vs am gear carb nc1 nc2 11 1: Mazda RX4 21 6 160 110 3.9 2.620 16.46 0 1 4 4 3 2 12 2:
Mazda RX4 Wag 21 6 160 110 3.9 2.875 17.02 0 1 4 4 3 2

Python
1 >>> mtcars_df=pd.read_csv(’mtcars.csv’) 2 >>> mtcars_df=mtcars_df.assign(∗∗{’nc1’:1,’nc2’:2}) 3 >>>
mtcars_df[’nc1’]=10 4 >>> mtcars_df.head(2) 5 name mpg cyl disp hp ... am gear carb nc1 nc2 6 0 Mazda RX4
21.0 6 160.0 110 ... 1 4 4 10 2 7 1 Mazda RX4 Wag 21.0 6 160.0 110 ... 1 4 4 10 2 8 9 [2 rows x 14 columns] 10
>>> mtcars_df.loc[mtcars_df.cyl==6,’nc1’]=3 11 >>> mtcars_df.head(2)
12 name mpg cyl disp hp ... am gear carb nc1 nc2
13 0 Mazda RX4 21.0 6 160.0 110 ... 1 4 4 3 2
14 1 Mazda RX4 Wag 21.0 6 160.0 110 ... 1 4 4 3 2
15
16 [2 rows x 14 columns]
También podemos combinar la técnica de indexación de filas con la actualización de columnas

R
1 > mtcars_dt=fread(’mtcars.csv’) 2 > setkey(mtcars_dt,’cyl’) 3 > mtcars_dt[,‘:=‘(nc1=1,nc2=2)] 4 > mtcars_dt[.
(4),nc2:=4] # change nc2 for rows with cyl=4 5 > head(mtcars_dt ,2) 6 name mpg cyl disp hp drat wt qsec vs am
gear carb nc1 nc2 7 1: Datsun 710 22.8 4 108.0 93 3.85 2.32 18.61 1 1 4 1 1 4 8 2: Merc 240D 24.4 4 146.7 62
3.69 3.19 20.00 1 0 4 2 1 4

Python
1 >>> mtcars_df=pd.read_csv(’mtcars.csv’) 2 >>> mtcars_df.set_index(’cyl’, inplace=True) 3 >>>
mtcars_df=mtcars_df.assign(∗∗{’nc1’:1,’nc2’:2}) 4 >>> mtcars_df.loc[4, ’nc2’]=4 # change nc2 for rows with
cyl=4 5 >>> mtcars_df.loc[4].head(2) 6 name mpg disp hp drat ... am gear carb nc1 nc2 7 cyl ... 8 4 Datsun 710
22.8 108.0 93 3.85 ... 1 4 1 1 4 9 4 Merc 240D 24.4 146.7 62 3.69 ... 0 4 2 1 4

Además de :=, también podemos usar la función set para modificar valores en una tabla de datos.
Cuando se usa correctamente, la ganancia de rendimiento puede ser significativa. Usemos un ficticio También
podemos combinar la técnica de indexación de filas con la actualización de columnas. Cas
R
1 > arr=array(0, c(1000,1000)) # create a big array 2 > dt=data.table(arr) # create a data.table based on the
array 3 > system.time(for (i in 1:nrow(arr)) arr[i,1L] = i) # modify the array 4 user system elapsed 5 0.003 0.000
0.003 6 > system.time(for (i in 1:nrow(arr)) dt[i,V1:=i]) # use := for data.table 7 user system elapsed 8 1.499
0.017 0.383 9 > system.time(for (i in 1:nrow(arr)) set(dt,i,2L,i)) # use set for data.table 10 user system elapsed
11 0.003 0.000 0.003

Vemos que actualizar los valores con la función set en este ejemplo es tan rápido como actualizar los valores en
una matriz.

Agrupar por también funciona en varias columnas.


También podemos crear varias columnas con agrupar por. Mi sensación es data.table es más expresivo.
R
1 > mtcars_dt[,.(mean_mpg=mean(mpg), max_hp=max(hp)), by=.(cyl, gear)] 2 cyl gear mean_mpg max_hp 3 1:
6 4 19.750 123 4 2: 4 4 26.925 109 5 3: 6 3 19.750 110 6 4: 8 3 15.050 245 7 5: 4 3 21.500 97 8 6: 4 5 28.200 113
9 7: 8 5 15.400 335 10 8: 6 5 19.700 175

Python
1 >>> mtcars_df.groupby([’cyl’,’gear’]).apply(lambda e:pd.Series({ ’mean_mpg’:e.mpg.mean(),
’max_hp’:e.hp.max()})) 2 mean_mpg max_hp 3 cyl gear 4 4 3 21.500 97.0 5 4 26.925 109.0 6 5 28.200 113.0 7 6
3 19.750 110.0 8 4 19.750 123.0
9 5 19.700 175.0 10 8 3 15.050 245.0 11 5 15.400 335.0
En data.table, también hay una palabra clave llamada keyby que permite agrupar y ordenar operaciones
juntas .
R
1 > mtcars_dt[,.(mean_mpg=mean(mpg),max_hp=max(hp)), keyby=.(cyl, gear)] 2 cyl gear mean_mpg max_hp 3
1: 4 3 21.500 97 4 2: 4 4 26.925 109 5 3: 4 5 28.200 113 6 4: 6 3 19.750 110 7 5: 6 4 19.750 123 8 6: 6 5 19.700
175 9 7: 8 3 15.050 245 10 8: 8 5 15.400 335
Incluso es posible agregar expresiones después de la palabra clave.

R
1 > mtcars_dt[,.(mean_mpg=mean(mpg),max_hp=max(hp)), by=.(cyl==8, gear==4)] 2 cyl gear mean_mpg
max_hp 3 1: FALSE TRUE 24.53333 123 4 2: FALSE FALSE 22.85000 175 5 3: TRUE FALSE 15.10000 335

3.6 UNIRSE
Join combina columnas de una o más tablas para RDBMS. También tenemos disponible la operación Join en
data.table y pandas. Aquí solo hablamos de tres tipos diferentes de combinaciones, es decir, combinación
interna, combinación izquierda y combinación derecha. La combinación izquierda y la combinación derecha
también se denominan combinación externa.
Hagamos dos mesas para unir.
Figura 3.1: Unión interna, unión izquierda (externa) y unión derecha (externa)
R
1 > department_dt=data.table(department_id=c(1, 2, 3),
department_name=c("Engineering","Operations","Sales")) 2 > department_dt 3 department_id
department_name 4 1: 1 Engineering 5 2: 2 Operations 6 3: 3 Sales 7 >
employee_dt=data.table(employee_id=c(1,2,3,4,5,6), department_id=c(1,2,2,3,1,4)) 8 > employee_dt 9
employee_id department_id 10 1: 1 1 11 2: 2 2 12 3: 3 2 13 4: 4 3 14 5: 5 1 15 6: 6 4

Python
>>> department_df=pd.DataFrame({’department_id’:[1,2,3],’ department_name’:
["Engineering","Operations","Sales"]}) 2 >>> department_df 3 department_id department_name 4 0 1
Engineering 5 1 2 Operations 6 2 3 Sales 7 >>> employee_df=pd.DataFrame({’employee_id’:[1,2,3,4,5,6],’
department_id’:[1,2,2,3,1,4]}) 8 >>> employee_df 9 employee_id department_id 10 0 1 1 11 1 2 2 12 2 3 2
13 3 4 3 14 4 5 1 15 5 6 4

También podría gustarte