Spark
Una breve introducción con Python II
9 de junio de 2024
1
1. Nociones básicas
1.1. RDDs
Un RDD (Resilient Distributed Dataset) es una colección de elementos dis-
tribuida a través de varios nodos de un clúster que se puede operar en paralelo.
Estos elementos pueden ser cualquier tipo de datos, como números, cadenas,
objetos Python, etc.
El término resilient”se refiere a la capacidad de los RDDs para recuperarse
automáticamente de los fallos. Spark logra esta resiliencia mediante el segui-
miento del linaje de cada RDD, es decir, de las operaciones que se utilizaron
para crearlo. De esta manera, si un nodo falla, Spark puede reconstruir el RDD
afectado utilizando la información de linaje y los datos almacenados en otros
nodos.
En Spark, todo el procesamiento se realiza mediante la creación, transfor-
mación y operación de RDDs. Estas operaciones se pueden realizar de manera
distribuida en paralelo en el clúster de computadoras, lo que permite un proce-
samiento eficiente de grandes volúmenes de datos.
Los RDDs pueden contener cualquier tipo de datos de Python e incluso
pueden contener tipos de datos definidos por el usuario. Esto significa que los
RDDs son flexibles y pueden adaptarse a una amplia gama de aplicaciones y
tipos de datos.
1.1.1. Particiones
Una partición en Apache Spark se refiere a una porción de los datos de un
RDD (Resilient Distributed Dataset) que se almacena y procesa en un único no-
do del clúster. Los RDD se dividen en particiones para permitir el procesamiento
paralelo y la distribución de datos en el clúster.
Cuando se crea un RDD, ya sea cargándolo desde el almacenamiento externo
o al realizar transformaciones en otro RDD, Spark lo divide automáticamente
en un número predeterminado de particiones. Estas particiones son las unidades
básicas de procesamiento y distribución en el clúster.
El número de particiones predeterminado se determina según la configura-
ción del clúster y el tamaño de los datos de entrada. Sin embargo, también
es posible especificar el número de particiones al crear un RDD utilizando el
parámetro numPartitions.
Cada tarea de Spark procesa una partición de datos en paralelo, por lo que
el número de particiones influye en la eficiencia del procesamiento paralelo. De-
masiadas particiones pueden aumentar el costo de la coordinación entre nodos,
mientras que muy pocas particiones pueden no aprovechar completamente los
recursos del clúster.
1.2. Dinámica de RDDs
Las fases de creación, transformación y acción constituyen el ciclo de vida
principal en el procesamiento de datos en Spark, y todas las operaciones se
2
realizan a través de la manipulación de RDDs en estas fases.
En Spark, cuando se trabaja con RDDs (Resilient Distributed Datasets),
las operaciones de transformación no se ejecutan de inmediato cuando se defi-
nen. En su lugar, se crean un DAG (grafo acı́clico dirigido) que representa las
operaciones de transformación que se deben aplicar al RDD base para llegar al
resultado deseado. Este DAG es una representación lógica de las operaciones de
transformación y no se traduce en operaciones de ejecución reales hasta que se
realiza una acción.
Las transformaciones en un RDD son operaciones como map(), filter(), flat-
Map(), etc., que transforman un RDD en otro RDD aplicando alguna función
a cada elemento del RDD original. Cuando se llama a estas transformaciones,
Spark simplemente agrega la operación correspondiente al DAG sin realizar
ningún cálculo real en los datos. Esto significa que Spark no calcula ni procesa
los datos inmediatamente después de llamar a una transformación; solo registra
la operación en el DAG.
Por otro lado, las acciones son operaciones que desencadenan la ejecución
real del DAG y la computación de los datos en el RDD. Algunos ejemplos de
acciones son collect(), count(), take(), saveAsTextFile(), etc. Cuando se llama a
una acción, Spark recorre el DAG de atrás hacia adelante (desde la acción hasta
las transformaciones) y ejecuta las operaciones de transformación necesarias
en los datos para calcular el resultado solicitado. Este proceso se conoce como
”materialización”del RDD.
En resumen, las transformaciones en un RDD no se calculan realmente hasta
que se realiza una acción. Esto permite a Spark optimizar el procesamiento
de datos al posponer la computación hasta que sea necesaria y al permitir la
optimización de consultas mediante la fusión y la planificación de operaciones.
1.3. Inicio y parada de Spark
Lo siguiente es un esquema de los programas utilizando Spark en entorno de
Python que lo habilite (como Google Colab).
1 from pyspark import SparkContext
2 sc = SparkContext("local[*]")
3 # (su codigo aqui)
4 sc.stop()
El argumento ”local[*].especifica que Spark se ejecutará en modo local utili-
zando todos los núcleos de CPU disponibles en la máquina. Esto es útil para el
desarrollo y pruebas en un entorno de una sola máquina.
La última lı́nea sc.stop() detiene el contexto Spark una vez que se ha comple-
tado el código. Esto libera los recursos utilizados por Spark y finaliza la sesión
de Spark. Es importante detener el contexto Spark cuando ya no se necesite
para evitar fugas de recursos.
3
1.4. Creación, lectura y escritura de RDDs
Un RDD es un objeto de la clase pyspark.rdd.RDD. Esta clase es parte de
la API de PySpark y se utiliza para representar conjuntos de datos distribuidos
que pueden ser procesados de manera paralela en un clúster de computadoras.
Un RDD en PySpark es esencialmente una colección inmutable y distribuida
de elementos que se pueden operar en paralelo. Estos elementos pueden ser
de cualquier tipo de datos de Python, como números, cadenas, listas, tuplas,
objetos personalizados, etc.
La clase pyspark.rdd.RDD proporciona una serie de métodos y operacio-
nes que permiten crear, transformar y operar sobre RDDs. Algunos ejemplos de
operaciones comunes en RDDs incluyen map, filter, reduce, entre otros.
A continuación se verán más en detalle las operaciones que se pueden realizar
a los RDDs a través de métodos del objeto RDD. Dichos métodos son sólo de
creación, lectura y escritura de datos.
1. parallelize
El método parallelize en PySpark se utiliza para crear un RDD (Resilient
Distributed Dataset) a partir de una colección de datos en Python, como
una lista. Este método toma la colección de datos y la distribuye de manera
uniforme entre los nodos del clúster de Spark, lo que permite que los datos
sean procesados de manera distribuida y paralela.
Observación 1.4.1. El método parallelize no está paralelizando ningún
proceso. Solo transforma una estructura de datos Python en una Spark,
distribuida, preparada para que Spark pueda procesarlos en paralelo.
El método parallelize toma dos argumentos principales:
Colección de datos
La primera es la colección de datos que se desea paralelizar y convertir
en un RDD. Esto puede ser una lista de elementos en Python u otra
colección iterable.
Número de particiones (opcional)
El segundo argumento es el número opcional de particiones en las
que se dividirá el RDD resultante. Por defecto, Spark intenta deter-
minar automáticamente el número óptimo de particiones basado en
el tamaño de los datos y la configuración del clúster.
Observación 1.4.2. El método parallelize en PySpark está diseñado para
tomar una colección de datos de Python, como una lista, y convertirla en
un RDD. Por lo tanto, no se puede pasar un solo entero o una cadena
directamente a parallelize, ya que espera una colección de datos iterable.
Ejemplo 1.4.1. El siguiente es un ejemplo en el que a partir de una lista
se crea un objeto RDD con el método parallelize.
4
1 from pyspark import SparkContext
2 sc = SparkContext("local[*]")
3 res = sc.parallelize([1, 2, 3, 4])
4 sc.stop()
No se pone nada en el segundo argumento porque por ahora no se está
interesado.
2. textFile
Es un método en PySpark que se utiliza para leer datos de archivos de
texto y crear un RDD a partir de ellos. Este método carga el contenido de
los archivos de texto y lo distribuye entre los nodos del clúster de Spark,
lo que permite procesar los datos de manera distribuida y paralela.
Observación 1.4.3. Cada elemento del RDD creado tiene una lı́nea de
texto.
El método textFile toma como argumento la ruta del archivo o la carpeta
que contiene los archivos de texto que se desean leer. Puede ser una ruta
local en el sistema de archivos del nodo maestro o una URL para acceder
a archivos remotos. También es posible especificar un número opcional de
particiones para dividir los datos en el RDD resultante.
Si se está utilizando un servicio en la nube o un servicio de notebooks como
Google Colab, se puede cargar el archivo de texto directamente desde tu
entorno. Se puede cargar el archivo manualmente en el entorno o utilizar
funciones especı́ficas del servicio para cargar archivos.
Ejemplo 1.4.2. El siguiente código muestra cómo a partir de la ruta de
un archivo de texto (cadena) se genera un RDD.
1 from pyspark import SparkContext
2 sc = SparkContext("local[*]")
3 res = sc.textFile("/prb.txt")
4 sc.stop()
Se tiene, por tanto,
También con el método textFile se puede leer una carpeta con varios
archivos de texto.
Ejemplo 1.4.3. Lo siguiente es un ejemplo de la lectura de una carpeta
con dos archivos de texto en RDD.
1 from pyspark import SparkContext
2 sc = SparkContext("local[*]")
3 res = sc.textFile("/pruebas")
4 sc.stop()
5
Fijarse que se da la ruta como argumento.
Se utiliza la ruta de la carpeta esta vez.
3. saveAsTextFile
Es un método en PySpark que se utiliza para guardar el contenido de un
RDD en archivos de texto en un sistema de archivos. Este método toma
como argumento la ruta del directorio de salida donde se guardarán los
archivos de texto.
Observación 1.4.4. En Google Colab hay una carpeta llamada content.
El método saveAsTextFile tiene como argumento en este caso un nombre,
a poner por el usuario, nuevo de una carpeta. Dicha carpeta se creará una
vez ejecutado el método y será ahı́ donde se irán almacenando los datos
del RDD. De este modo, reiterando, el argumento es un nombre inédito.
No puede haber otra carpeta en el directorio de Google Colab con el mismo
nombre porque de lo contrario va a haber errores.
El método saveAsTextFile guarda el contenido del RDD en archivos de
texto en el directorio especificado, escribiendo una lı́nea por cada elemen-
to del RDD. Cada partición del RDD se escribe en un archivo de texto
separado dentro del directorio de salida. Si no se especifica una ruta de
salida, se guardará en el directorio actual.
Ejemplo 1.4.4. Se crea una nueva carpeta con el nombre indicado donde
se almacena la información del RDD. En esto caso los datos de los archivos
de texto de la carpeta pruebas.
1 sc.stop()
2 from pyspark import SparkContext
3 sc = SparkContext("local[*]")
4 res = sc.textFile("/pruebas")
5 res.saveAsTextFile("nombreNuevoDeCarpeta")
6 sc.stop()
Se puede ver que se ha puesto un sc.stop() previo al código porque ası́ se
aseguran cierres previos de contextos tipo Spark.
1.5. Transformaciones
Las transformaciones son operaciones que se aplican a un conjunto de datos
distribuido llamado RDD (Resilient Distributed Dataset) para producir otro
RDD. Estas operaciones no se ejecutan de inmediato, sino que forman un grafo
de ejecución que se activa cuando se llama a una acción.
Las transformaciones en Spark son perezosas, lo que significa que no se ejecu-
tan inmediatamente después de ser llamadas. En su lugar, se construye un plan
de ejecución (llamado grafo de transformaciones) que se ejecuta solo cuando se
invoca una acción en el RDD resultante.
6
Las transformaciones en Spark pueden ser estrechas o anchas. Las transfor-
maciones estrechas, como map o filter, implican operaciones en una solo parti-
ción de datos y no requieren la redistribución de los datos. Por otro lado, las
transformaciones anchas, como operaciones de agrupamiento (groupByKey) o
de ordenación (sortByKey), implican la redistribución de datos entre las parti-
ciones, lo que requiere un intercambio de datos (shuffle) y puede ser más costoso
en términos de rendimiento.
Los RDD en Spark son inmutables, lo que significa que una vez que se
crea un RDD, no se puede modificar. Cada transformación aplicada a un RDD
devuelve un nuevo RDD en lugar de modificar el RDD original. Esto permite
un procesamiento paralelo y distribuido de datos sin preocuparse por los efectos
secundarios de las modificaciones concurrentes.
Son equivalentes las sintaxis de las siguientes transformaciones.
1 res = sc.parallelize(...).filter(f).map(g).reduce(h)
1 res = sc.parallelize(...) \
2 .filter(f) \
3 .map(g) \
4 .reduce(h)
1 rdd = sc.parallelize(...)
2 rdd2 = rdd.filter(f)
3 rdd3 = rdd2.map(g)
Observación 1.5.1. En las dos primeras opciones, el usuario solo tiene acceso
al RDD final res, ya que todas las operaciones están encadenadas en una sola
lı́nea. Esto puede ser útil si solo se está interesado en el resultado final y no se
necesita los RDD intermedios para ningún otro propósito.
En la última opción, como las operaciones se dividen en pasos individuales
y se asignan a variables intermedias (rdd, rdd2, rdd3), el usuario tiene la flexi-
bilidad de acceder a cualquiera de estos RDD intermedios si es necesario para
algún otro cálculo o análisis.
1. Transformaciones estrechas
Las transformaciones estrechas en Apache Spark son aquellas operaciones
en un RDD (Resilient Distributed Dataset) que pueden ejecutarse sin ne-
cesidad de redistribuir los datos entre las particiones. Esto significa que
cada partición de salida solo depende de una única partición de entrada.
Recordatorio 1.5.1. Una partición es una porción de los datos de un
RDD que se procesa de forma independiente en un nodo del clúster, per-
mitiendo el procesamiento paralelo y distribuido de los datos.
En otras palabras, durante la ejecución de una transformación estrecha,
cada tarea puede procesar sus datos de manera independiente sin nece-
sidad de comunicarse con otras particiones de datos en el clúster. Esto
7
permite que las transformaciones estrechas sean altamente eficientes en
términos de procesamiento paralelo.
map(fun) El método map se utiliza para transformar cada elemento
de un RDD utilizando una función dada.
Toma una función como argumento que se aplica a cada elemento
del RDD, produciendo un nuevo RDD donde cada elemento es el
resultado de aplicar la función a un elemento correspondiente del
RDD original.
El RDD resultante tendrá el mismo número de elementos que el RDD
original.
Ejemplo 1.5.1. Se toma una lista de números a la que se suma 1 a
cada elemento.
1 nums = sc.parallelize([1, 2, 3, 3])
2 nums.map(lambda x: x + 1) # -> {2, 3, 4, 4}
Ejemplo 1.5.2. Se toma una lista de lineas de texto y cada una de
ellas se descompone a su vez en una lista según el espaciado.
1 frases = sc.parallelize(["hello world", "hi you"])
2 frases.map(lambda linea: linea.split()) # -> {["hello", "world
"], ["hi", "you"]}
Ejemplo 1.5.3. Se toma una lista de listas de numeros y cada una
de ellas permanece invariante para la transformación.
1 listas = sc.parallelize([[1, 2, 3], [4, [5, 6]], [1]])
2 listas.map(lambda x: x) # -> {[1, 2, 3], [4, [5, 6]], [1]}
filter(fun) El método filter se utiliza para filtrar elementos de un RDD
basándose en una condición dada.
Toma una función de predicado como argumento que devuelve True
o False para cada elemento del RDD.
Produce un nuevo RDD que contiene solo los elementos para los
cuales la función de predicado devuelve True.
Ejemplo 1.5.4. Se seleccionan los elementos que sean mayores que
dos.
1 nums = sc.parallelize([1, 2, 3, 3])
2 nums.filter(lambda x: x > 2) # -> {3, 3}
flatMap(fun) El método flatMap es similar a map, pero se utiliza
cuando cada elemento del RDD de entrada puede mapearse a cero,
uno o varios elementos en el RDD de salida.
8
Mientras que map transforma cada elemento de un RDD en otro
elemento (uno a uno), flatMap transforma cada elemento en cero o
más elementos y luego ’aplana’ estos resultados en un nuevo RDD.
Imagina que tienes un RDD que contiene palabras. Si aplicas map
y a cada palabra le aplicas una función que devuelve una lista de
palabras relacionadas, obtendrás un RDD de listas de palabras. Por
ejemplo:
1 words_rdd = sc.parallelize(["hello", "world"])
2 mapped_rdd = words_rdd.map(lambda word: [word, word.upper(),
word.lower()])
3
4 # El resultado de mapped_rdd seria:
5 # [[’hello’, ’HELLO’, ’hello’], [’world’, ’WORLD’, ’world’]]
Sin embargo, si se aplica flatMap, se obtendrá un RDD donde todas
las palabras resultantes se ’aplanan’ en una sola lista:
1 flat_mapped_rdd = words_rdd.flatMap(lambda word: [word, word.
upper(), word.lower()])
2
3 # El resultado de flat_mapped_rdd seria:
4 # [’hello’, ’HELLO’, ’hello’, ’world’, ’WORLD’, ’world’]
Entonces, flatMap se utiliza cuando deseas generar cero, uno o múlti-
ples elementos para cada elemento de entrada y deseas que estos ele-
mentos se ’aplanen’ en un solo RDD, en lugar de crear un RDD de
listas o tuplas.
Ejemplo 1.5.5. A partir de la lista frases se ’aplanan’ los elementos
de tipo cadena formándose una sola lista.
1 frases = sc.parallelize(["hello world", "hi you"])
2 frases.flatMap(lambda linea: linea.split()) # -> {"hello", "
world", "hi", "you"}
Ejemplo 1.5.6. A partir de la lista de listas de números se ’aplana’
dicha lista, que es la misma de la de partida, haciendose que se genere
un nueva única lista que contiene todos los elementos pertenecientes
a alguna de las listas de la lista original.
1 listas = sc.parallelize([[1, 2, 3], [4, [5, 6]], [1]])
2 listas.flatMap(lambda x: x) # -> {1, 2, 3, 4, [5, 6], 1}
2. Transformaciones anchas
Una transformación ancha (wide transformation) en Apache Spark es una
operación que requiere la redistribución de los datos entre las particiones
9
del RDD. A diferencia de las transformaciones estrechas, las transforma-
ciones anchas implican la mezcla y redistribución de datos entre las parti-
ciones, lo que puede requerir comunicación y coordinación entre los nodos
del clúster.
En una transformación ancha, cada partición de salida puede depender
de múltiples particiones de entrada, lo que implica que los datos de va-
rias particiones deben agruparse, combinar u ordenarse antes de continuar
con la operación. Esto puede resultar en una mayor comunicación y mo-
vimiento de datos entre nodos, lo que puede impactar en el rendimiento y
la escalabilidad de la aplicación.
Teniendo en cuenta lo siguiente.
1 nums = sc.parallelize([1, 2, 3, 3])
2 nums2 = sc.parallelize([3, 3, 5])
Se tienen las siguientes operaciones/métodos.
distinct
Se utiliza para eliminar duplicados de un RDD, dejando solamente
los elementos únicos.
1 nums.distinct() # -> {1, 2, 3}
union(...)
Se utiliza para combinar dos RDDs en uno solo, conservando todos
los elementos de ambos RDDs.
1 nums.union(nums2) # {1, 2, 3, 3, 3, 3, 5} -> union como
multiconjuntos
intersection(...)
Se utiliza para obtener la intersección de dos RDDs, es decir, devuelve
un nuevo RDD que contiene solo los elementos que están presentes
en ambos RDDs.
1 nums.intersection(nums2) # -> {3} - elimina duplicados
subtract(...)
Se utiliza para obtener la diferencia entre dos RDDs, es decir, de-
vuelve un nuevo RDD que contiene solo los elementos presentes en el
primer RDD y no en el segundo RDD.
1 nums.subtract(nums2) # -> {1, 2}
cartesian(...)
Se utiliza para calcular el producto cartesiano de dos RDDs. Es-
te método devuelve un nuevo RDD que contiene todas las posibles
combinaciones de elementos entre los dos RDDs originales.
10
1 nums.cartesian(nums2) # puede ser *muy* costosa
2 # -> {(1, 3), (1, 3), (1, 5), (2, 3), (2, 3), (2, 5), (3, 3),
(3, 3), (3, 5), (3, 3), (3, 3), (3, 5)}
zipWithIndex
Se utiliza para agregar un ı́ndice a cada elemento de un RDD, creando
ası́ un nuevo RDD que contiene tuplas de la forma (elemento, ı́ndice).
1 nums.zipWithIndex() # -> {(1, 0), (2, 1), (3, 2), (3, 3)}
sample(conReemplazo, fracción)
El método sample() en Spark se utiliza para tomar una muestra alea-
toria de elementos de un RDD. Esta muestra puede ser con o sin re-
emplazo, y se especifica mediante el parámetro conReemplazo (true o
false). La fracción de elementos a tomar se especifica con el parámetro
fracción.
Cuando se utiliza sample() en un RDD, Spark selecciona aleatoria-
mente una fracción de los elementos del RDD según la fracción espe-
cificada. Si se especifica conReemplazo=True, Spark permite que un
mismo elemento aparezca varias veces en la muestra (es decir, con
reemplazo). Si conReemplazo=False, cada elemento seleccionado se
eliminará del RDD antes de seleccionar el siguiente, lo que garantiza
que no haya duplicados en la muestra.
Ejemplo 1.5.7. En este ejemplo, sampled rdd contendrá una mues-
tra aleatoria del 50 % de los elementos del RDD original, permitiendo
duplicados (con reemplazo). La muestra se recopila y se muestra uti-
lizando el método collect().
1 # Crear un RDD con algunos elementos
2 rdd = sc.parallelize(range(10))
3
4 # Tomar una muestra aleatoria del 50 % de los elementos con
reemplazo
5 sampled_rdd = rdd.sample(True, 0.5)
6
7 # Recopilar y mostrar el resultado
8 print(sampled_rdd.collect())
El segundo argumento del método sample() es una fracción que re-
presenta la proporción de elementos que se tomarán en la muestra
aleatoria. Esta fracción debe ser un número decimal en el rango de
0 a 1, indicando la proporción de elementos que se seleccionarán en
relación al tamaño total del RDD.
Si se especifica un valor mayor que 1 para el segundo argumento,
Spark tomará una muestra aleatoria con reemplazo de tamaño igual
al valor especificado. Es decir, en lugar de interpretarse como una
11
fracción, se interpreta como un número absoluto de elementos a se-
leccionar.
Por ejemplo, si se establece fracción = 2, Spark tomará una muestra
aleatoria con reemplazo de tamaño 2, lo que significa que seleccio-
nará dos elementos aleatorios del RDD. Si el RDD tiene menos de 2
elementos, Spark tomará todos los elementos disponibles.
Ejemplo 1.5.8. En este ejemplo, sampled rdd contendrá una mues-
tra aleatoria de tamaño 3 con reemplazo de los elementos del RDD
original. Si el RDD tiene más de 3 elementos, algunos elementos pue-
den aparecer más de una vez en la muestra debido al reemplazo.
1 # Crear un RDD con algunos elementos
2 rdd = sc.parallelize(range(10))
3
4 # Tomar una muestra aleatoria de 3 elementos con reemplazo
5 sampled_rdd = rdd.sample(True, 3)
6
7 # Recopilar y mostrar el resultado
8 print(sampled_rdd.collect())
1.6. Acciones
Las acciones son operaciones que se realizan en un RDD para obtener re-
sultados concretos o llevar a cabo acciones especı́ficas en los datos distribuidos.
Mientras que las transformaciones definen cómo se manipulan los datos, las
acciones son las que desencadenan la ejecución real de las transformaciones y
pueden provocar que Spark realice cómputos y operaciones en el clúster distri-
buido.
Las acciones en Spark son operaciones que provocan que se desencadenen
las transformaciones en el RDD y que se realice algún tipo de operación sobre
los datos distribuidos. Algunas acciones comunes en Apache Spark incluyen
collect(), count(), take(n), reduce(func), foreach(func), entre otras.
Teniendo en cuenta:
1 nums = sc.parallelize([1, 2, 3, 3])
2 palabras = sc.parallelize(["hola", "a", "viva", "yo"])
Se tienen las siguientes acciones/métodos.
reduce(fun)
Se utiliza para combinar los elementos de un RDD utilizando una función
de reducción. La función de reducción se aplica de manera iterativa a
los elementos del RDD, combinando dos elementos a la vez hasta que se
obtiene un resultado único.
Cuando se aplica reduce() a un RDD, Spark combina los elementos del
RDD utilizando la función de reducción proporcionada. Esta función debe
12
ser asociativa y conmutativa para garantizar que el resultado de la reduc-
ción sea el mismo independientemente del orden en que se combinen los
elementos.
1 nums.reduce(lambda x, y: x+y) # -> 9
fold(fun)
Es similar al método reduce(), pero con la adición de un valor inicial,
también conocido como ”valor cero.o ”valor neutral”. Esta función de re-
ducción con valor inicial se aplica de manera iterativa a los elementos del
RDD, combinando cada elemento con el valor inicial hasta obtener un
resultado final.
La función de reducción en fold() también debe ser asociativa y conmuta-
tiva, de manera similar a reduce(), para garantizar resultados consistentes
independientemente del orden de combinación de los elementos.
La diferencia clave entre fold() y reduce() es que fold() requiere un valor
inicial, mientras que reduce() no lo hace. El valor inicial se utiliza como
punto de partida para la función de reducción.
1 nums.fold(0, lambda x, y: x+y) # -> 9
2 sc.parallelize([]).fold(0, lambda x, y: x+y) # -> 0
collect()
Es una acción que se utiliza para recopilar todos los elementos de un
RDD y devolverlos como una lista en el programa de control. Es una de
las acciones más comunes en Spark y se utiliza para traer todos los datos
distribuidos en el RDD de vuelta al programa de control, lo que permite
trabajar con ellos en el entorno local de Python.
Cuando se aplica collect() a un RDD, Spark recopila todos los elementos
distribuidos en el RDD y los devuelve como una lista de Python en el
programa de control. Es importante tener en cuenta que si el RDD es
muy grande, collect() puede consumir muchos recursos de memoria en el
programa de control, ya que todos los datos deben caber en la memoria
del programa de control.
1 nums.collect() # -> [1, 2, 3, 3]
2 # devuelve todos los datos
take(n)
En Apache Spark es una acción que se utiliza para tomar los primeros
n elementos de un RDD y devolverlos como una lista en el programa de
control. Es similar al método collect(), pero en lugar de recopilar todos
los elementos del RDD, solo toma los primeros n elementos.
13
La diferencia principal entre take(n) y collect() radica en su eficiencia y en
el manejo de grandes conjuntos de datos. Mientras que collect() recopila
todos los elementos del RDD en el programa de control, lo que puede
consumir una cantidad significativa de recursos de memoria si el RDD es
grande, take(n) solo toma los primeros n elementos, lo que puede ser más
eficiente y práctico en algunos casos.
1 palabras.take(2) # -> {’hola’, ’a’} - los primeros segun se creo el
RDD
top(n,key)
Es una acción que se utiliza para devolver los n elementos más grandes de
un RDD, ordenados en orden descendente. Es similar al método take(),
pero en lugar de tomar los primeros elementos, top() toma los elementos
más grandes.
La diferencia principal entre top() y take() radica en que top() devuelve los
elementos ordenados en orden descendente, mientras que take() devuelve
los elementos en el orden en que aparecen en el RDD.
Puede tomar hasta dos argumentos.
El primer argumento es el número de elementos que se desean obtener, es
decir, los n elementos más grandes del RDD.
El segundo argumento es una función de comparación opcional que se uti-
liza para ordenar los elementos. Esta función debe tomar un único paráme-
tro y devolver un valor que pueda ser comparado.
Si no se proporciona la función de comparación, Spark ordenará los ele-
mentos en orden descendente utilizando el valor natural de los elementos
(por ejemplo, ordenar números de mayor a menor).
1 palabras.top(2, len) # -> {’hola’, ’viva’} - los primeros segun el
orden dado por la funcion len
2 palabras.top(2) # -> {’yo’, ’viva’} - los primeros segun el orden *
natural*; es decir, segun el ultimo elemento hacia el primero:
en orden descendente
count
Es una acción que se utiliza para contar el número total de elementos en
un RDD. Cuando se aplica count() a un RDD, Spark realiza una operación
de conteo en paralelo en los nodos del clúster y devuelve el número total
de elementos en el RDD.
1 palabras.count() # -> 4
countByValue
14
Es una acción que se utiliza para contar la frecuencia de cada valor único
en un RDD. Devuelve un diccionario donde las claves son los valores únicos
en el RDD y los valores son el número de veces que cada valor aparece en
el RDD.
1 nums.countByValue() # -> {1: 1, 2: 1, 3: 2} - diccionario valor:
contador
foreach
Es una acción que se utiliza para aplicar una función a cada elemento del
RDD, pero sin devolver ningún resultado al programa de control. Es útil
cuando se necesita realizar operaciones de lado en cada elemento del RDD,
como escribir los elementos en un archivo, actualizar datos en una base de
datos, enviar los elementos a un servicio externo, entre otras acciones.
Es importante tener en cuenta que foreach() realiza la operación en pa-
ralelo en los nodos del clúster, aplicando la función a cada elemento de
forma independiente en cada nodo.
1 palabras.foreach(print) # imprime las cuatro palabras
takeSample(conReemplazo,nr elems)
Se utiliza para tomar una muestra aleatoria de elementos de un RDD.
Esta acción toma dos argumentos principales:
• conReemplazo: un booleano que indica si se permite el reemplazo
al tomar la muestra. Si se establece en True, los elementos pueden
ser seleccionados más de una vez en la muestra. Si se establece en
False, cada elemento se selecciona exactamente una vez en la muestra
(muestreo sin reemplazo).
• nr elems: el tamaño de la muestra, es decir, el número de elementos
que se desean seleccionar al azar.
1 palabras.takeSample(conReemplazo, nr_elems)
1.6.1. Acciones para RDDs numéricos
mean()
Calcula la media de los elementos en el RDD.
Ejemplo 1.6.1. Se crea un RDD con números del 1 al 5 y luego se calcula
su media.
1 rdd = sc.parallelize([1, 2, 3, 4, 5])
2 media = rdd.mean()
3 print(media)
15
sum()
Calcula la suma de los elementos en el RDD.
Ejemplo 1.6.2. Se crea un RDD con números del 1 al 5 y luego se calcula
su suma
1 rdd = sc.parallelize([1, 2, 3, 4, 5])
2 suma = rdd.sum()
3 print(suma)
min()
Encuentra el valor mı́nimo en el RDD.
Ejemplo 1.6.3. Se crea un RDD con números del 1 al 5 y luego se
encuentra el valor mı́nimo.
1 rdd = sc.parallelize([1, 2, 3, 4, 5])
2 minimo = rdd.min()
3 print(minimo)
max()
Encuentra el valor máximo en el RDD.
Ejemplo 1.6.4. Se crea un RDD con números del 1 al 5 y luego se
encuentra el valor máximo.
1 rdd = sc.parallelize([1, 2, 3, 4, 5])
2 maximo = rdd.max()
3 print(maximo)
variance()
Calcula la varianza de los elementos en el RDD.
Ejemplo 1.6.5. Se crea un RDD con números del 1 al 5 y luego se calcula
su varianza.
1 rdd = sc.parallelize([1, 2, 3, 4, 5])
2 varianza = rdd.variance()
3 print(varianza)
sampleVariance()
Calcula la varianza muestral de los elementos en el RDD.
Ejemplo 1.6.6. Se crea un RDD con números del 1 al 5 y luego se calcula
su varianza muestral.
16
1 rdd = sc.parallelize([1, 2, 3, 4, 5])
2 varianza_muestral = rdd.sampleVariance()
3 print(varianza_muestral)
stdev()
Calcula la desviación estándar de los elementos en el RDD.
Ejemplo 1.6.7. Se crea un RDD con números del 1 al 5 y luego se calcula
su desviación estándar.
1 rdd = sc.parallelize([1, 2, 3, 4, 5])
2 desviacion_estandar = rdd.stdev()
3 print(desviacion_estandar)
sampleStdev()
Calcula la desviación estándar muestral de los elementos en el RDD.
Ejemplo 1.6.8. Se crea un RDD con números del 1 al 5 y luego se calcula
su desviación estándar muestral.
1 rdd = sc.parallelize([1, 2, 3, 4, 5])
2 desviacion_estandar_muestral = rdd.sampleStdev()
3 print(desviacion_estandar_muestral)
1.6.2. RDDs de pares
A partir de RDDs de pares ordenados, como los generados por listas de
tuplas, se pueden realizar varias transformaciones y acciones como las siguientes.
1. zip
1 rdd1 = sc.parallelize(range(0, 3))
2 rdd2 = sc.parallelize(range(10, 13))
3 rdd1.zip(rdd2) # -> {(0, 10), (1, 11), (2, 12)}
2. wholeTextFiles(çarpeta/de/datos”)
Se utiliza para leer archivos de texto completos como pares clave-valor,
donde la clave es la ruta del archivo y el valor es el contenido completo
del archivo como una cadena de texto.
1 sc.wholeTextFiles("carpeta/de/datos")
2 # {("archivo1.txt", "contenido"), ("archivo2.txt", "hola"), ...}
3. Varias transformaciones
17
1 pares = sc.parallelize([(’a’, 1), (’b’, 7), (’b’, 1), (’a’, 3)])
2 pares.reduceByKey(lambda x, y: x + y) # -> {(’a’, 4), (’b’, 8)}
3 pares.groupByKey() # -> {(’a’, [1, 3]), (’b’, [7, 1])}
4 pares.mapValues(lambda x: x + 1) # -> {(’a’, 2), (’b’, 8), (’b’, 2)
, (’a’, 4)}
5 pares.keys() # -> {’a’, ’b’, ’b’, ’a’}
6 pares.values() # -> {1, 7, 1, 3}
7 pares.sortByKey() # -> {(’a’, 1), (’a’, 3), (’b’, 7), (’b’, 1)}
8 pares.sortBy(lambda x: x[1]) # -> {(’a’, 1), (’b’, 1), (’a’, 3), (’
b’, 7)}
9 pares2 = sc.parallelize([(’a’, 11)])
10 pares.subtractByKey(pares2) # -> {(’b’, 7), (’b’, 1)}
11 pares.join(pares2) # -> {(’a’, (1, 11)), (’a’, (3, 11))}
4. Algunas acciones
1 pares.countByKey() # {’a’: 2, ’b’: 2} diccionario
2 pares.collectAsMap() # {’a’: 3, ’b’: 1} valores mas recientes
3 pares.lookup("a") # [1, 3]
18