Documentos de Académico
Documentos de Profesional
Documentos de Cultura
DATOS
Serie de datos grandes de Chapman & Hall/CRC
Éxitos y trampas
Uttam Ghosh, Danda B. Rawat, Raja Datta, AlSakib Khan Pathan
Nailon Zhang
Primera edición publicada en 2021 por
CRC Press
6000 Broken Sound Parkway NW, Suite 300, Boca Ratón, FL 334872742
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.
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)
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
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.
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
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
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.
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 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
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
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]
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]
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
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
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.
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
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
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))
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
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
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
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)
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).
1 > source(’benchmark.R’)
2 Unit: microseconds
5 neval
6 1000
7 Unit: microseconds
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
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.
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))
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.
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
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.
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
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
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.
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.
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
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.
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
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
3 cyl gear
Python
3 cyl gear
R
1 > key(mtcars_dt)
4 > key(mtcars_dt)
5 NULL
7 > key(mtcars_dt)
8 [1] "gear"
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
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.
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