💡 MATERIALES PARA LA ACTIVIDAD:

Script.

PREPARATIVOS INICIALES

Para no tener que cargar los paquetes dplyr y tidyr, se procede a activar el paquete tidyverse ya que incluye los dos anteriores como dependencias

library(tidyverse)

Establecer la carpeta de trabajo

setwd("D:/G2040/TEMA_1_Introduccion/TEMA_1_ejercicios")

Cargar el fichero

load("zonas_verdes.RData")

FILTRAR FILAS/CASOS (Función filter())

Esta función filtrar los casos (las filas) según una o más condiciones.

Un primer caso son aquellas filas que incluyen datos perdidos (NA). Si queremos eliminar todas las filas que poseen algún dato perdido

borrame <- zonas_verdes %>% 
  na.omit()

… o sólo aquellas que contienen NA en variables concretas.

borrame <- zonas_verdes %>% 
  filter(!is.na(obras))

También es posible eliminar filas con valores duplicados

borrame %>%
  distinct()

Por último, podríamos eliminar algunas filas que ocupan posiciones determinadas, por ejemplo, las filas 10, 20, y 40.

borrame %>%
  filter(!row_number() %in% c(10, 20, 40))

Igualmente, podemos eliminar filas que cumplen una codición específica, por ejemplo, aquellas filas que en el dataframe borrame pertenecen al barrio de Retiro y su superficie es mayor que 5 ha.

borrame <- zonas_verdes %>%
  select(barrio, superficie) %>%
  filter(barrio == "Retiro" & superficie > 6)

Un tipo particular de uso tiene que ver con la selección de fechas. Primero se debe comprobar que la variable en cuestión es de tipo Date.

str(zonas_verdes)

Si la variable no fuera de tipo Date, habría que convertirla usando la siguiente sintaxis: df <- df %>% mutate(fecha = as.Date(fecha)). Si además, el formato no fuera yyyy-mm-dd, podría usarse el siguiente: df <- df %>% mutate(fecha = as.Date(fecha, format = “%d/%m/%Y”)). Para filtrar un única fecha:

zonas_verdes %>%
  filter(fecha_inauguracion == as.Date("1999-08-28"))

Si se quiere un rango de fechas, hay dos posiblidades:

zonas_verdes %>%
  filter(fecha_inauguracion >= as.Date("1972-01-01"),
         fecha_inauguracion <= as.Date("2023-12-31"))

o también

zonas_verdes %>%
  filter(fecha_inauguracion >= as.Date("1972-01-01") & fecha_inauguracion <= as.Date("2023-03-31"))

Si sólo nos interesa filtar uno o varios unidades de tiempo, es necesario cargar el paquete lubridate.

library(lubridate)
zonas_verdes %>%
  filter(year(fecha_inauguracion) == 1944)                                 # Filtrar por año
zonas_verdes %>%
  filter(month(fecha_inauguracion) == 3)                                   # Filtrar por mes específico (ej. marzo)
zonas_verdes %>%
  filter(day(fecha_inauguracion) == 15)                                   # Filtrar por día del mes

Como resumen:

Acción Código base
Igualdad filter(fecha == as.Date(“2023-01-01”))
Rango de fechas filter(fecha >= … & fecha <= …)
Por año / mes / día filter(year(fecha) == 2023) (con lubridate)
Día de la semana filter(wday(fecha) == 2) o == “Mon”

FILTRAR VALORES ÚNICOS: Función distinct()

La función distinct() puede usarse para filtrar aquellos valores (únicos) que se repiten en una variable, por ejemplo, los de la variable parques

zonas_verdes %>% 
  distinct(parques)

Téngase en cuenta que sólo se devuelven los valores únicos de la columna solicitada. También es posible filtrar los valores equivalentes de varias variables simultáneamente.

zonas_verdes %>% 
  distinct(parques, obras)

Filtar los valores únicos de todas las columnas.

zonas_verdes %>% 
  distinct(obras)

En el caso de trabajar con cadenas de caracteres, se pueden filtrar siempre que añadamos la función grepl() a continuación de la función filter(), por ejemplo, filtrar filas de barrio que contienen la cadena Arg (de Arganzuela).

zonas_verdes %>% 
  filter(grepl("Arg", barrio))

Aquí se filtran las filas que que contienen una u otra cadena (|).

zonas_verdes %>% 
  filter(grepl("Ret|Sal", barrio))

También podemos pedir lo contrario, seleccionar aquellos caso que no contienen cierta combinación de caracteres.

zonas_verdes %>% 
  filter(!grepl("Ret", barrio))

Las condiciones pueden ser expresiones logicas construidas mediante los operadores relacionales y lógicos:

Símbolo Significado
< Menor que
> Mayor que
== Igual que
<= Menor o igual que
>= Mayor o igual que
!= Diferente que
%in% Pertenece al conjunto
is.na Es NA
!is.na No es NA
Simbolo Significado
& boolean and
| boolean o
xor or inclusivo
! not
any cualquiera true
all todos verdaderos

ORDENAR FILAS/CASOS (arrange())

La función arrange() se utiliza para ordenar las filas de un data frame de acuerdo a una o varias columnas/variables. Por defecto arrange() ordena las filas por orden ascendente:

zonas_verdes %>% 
  arrange(superficie)

Para ordenar de manera descendente, se anida la función desc().

zonas_verdes %>% 
  arrange(desc(superficie))

Los casos ausentes (NA’s) aparecen ordenados al final, tanto con orden ascente como descendente:

Para organizar las filas según varias columnas, simplemente se proporcionan más nombres de columna como argumentos:

zonas_verdes %>% 
  arrange(barrio, superficie)

Es posible combinar una columna en orden ascendente y otra en orden descendente:

zonas_verdes %>% 
  arrange(barrio, desc(superficie))

También es factible ordenar las filas de acuerdo con orden preestablecido. Para ello, es necesario incluir un factor con unos niveles ordeandos de manera específica.

zonas_verdes %>% 
  arrange(factor(obras, levels = c("NA", "NO", "SI")))

Hay otras opciones para ordenar el dataframe. Por ejemplo, ordenar las filas de acuerdo con un orden prefijado de la variable barrio y, a continuación, utilizar los valores de la variable superficie (orden ascendente). Esto se consigue usando la función match()dentro de la función arrange(). La función match() obtiene el índice de fila de los valores de la columna barrio y, a continuación, la función arrange() ordenar en función de estos valores de índice. Para ordenar en función de los valores de puntos descendentes, basta con utilizar desc() en su lugar.

zonas_verdes %>%
  arrange(match(barrio, c("Arganzuela", "Retiro")), superficie)

También es posible ordenar los casos según grupos de variables. Por ejemplo, es posible ordenar los casos según la variable superficie según un orden ascendente, pero agrupados según la variable barrio

zonas_verdes %>%
  group_by(barrio) %>%
  arrange(superficie, .by_group=TRUE)

En el caso de utilizar un orden descendente

zonas_verdes %>%
  group_by(barrio) %>%
  arrange(desc(superficie), .by_group=TRUE)

También es posible organizar las filas según múltiples grupos:

zonas_verdes %>%
  group_by(barrio, obras) %>%
  arrange(superficie, .by_group=TRUE)

También es posible ordenar variables como cadenas.

zonas_verdes %>%
  arrange(desc(barrio))

COMBINAR DOS O MÁS DATAFRAMES (añadir columnas/filas)

Hay dos posiblidades;

bind_rows().

Es la función clásica de dplyr/tidyverse para unir filas de dos o más data frames. La sintaxis es bind_rows(df1, df2, df3, .id = "source"). Peculiaridades:

  • Combina cualquier cantidad de data frames.
  • Rellena con NA columnas que no existen en todos los data frames.
  • La opción .id crea una columna que indica de qué data frame proviene cada fila.
df1 <- data.frame(a = 1:2, b = 3:4)
df2 <- data.frame(a = 5:6, c = 7:8)
bind_rows(df1, df2)

add_row()

Otra posible opción es añadir nuevas filas a un dataframe preexistente mediante la función add_row(), que agrega filas a un dataframe existente, una fila a la vez o de manera programática. Presenta la siguiente sintaxis básica:

add_row(.data, .before=NULL, .after=NULL)

donde:

  • .data: El nombre del marco de datos existente al que se van a añadir filas.
  • .before: El índice de la fila antes de la cual se va a añadir la nueva fila.
  • .after: El índice de la fila después de la cual se va a añadir la nueva fila.

Tenga en cuenta que si no especifica ningún valor para los argumentos .before o .after, la nueva fila simplemente se añadirá al final del data frame.

A continuación se crea un nuevo dataframe denominado zonas_verdes_actualizado.

zonas_verdes_actualizado <- data.frame(ID=c(101, 102, 103, 104),
                 barrio=c("Monte Carmelo", "Las Tablas", "Valdebebas", "Valdebebas"),
                 parques=c(2, 8, 1, 5),
                 superficie=c(3.021, 2.807, 2.456, 3.632), 
                 obras = c(0, 1, NA, 0),
                 fecha_inauguracion = c("2024-12-05", "2005-09-20", "2012-07-01", "2000-03-06"))
zonas_verdes_actualizado

Hemos olvidado un nuevo caso en el dataframe zonas_verdes_actualizado, por lo que lo creamos y lo unimos al anterior dataframe.

zonas_verdes_actualizado %>% 
  add_row(ID=105, barrio = "Las Tablas", parques = 3, superficie = 0.87, obras = 1, fecha_inauguracion = "2025-10-01")

Puede ser interesante unir a este dataframe un nuevo caso, por ejemplo al inicio…

zonas_verdes_actualizado %>% 
  add_row(ID=100, barrio = "Sanchinarro", parques = 6, superficie = 3.7, obras = NA, fecha_inauguracion = "2023-01-10", .before=1)

… o al final

zonas_verdes_actualizado %>% 
  add_row(ID=107, barrio = "Sanchinarro", parques = 6, superficie = 3.7, obras = NA, fecha_inauguracion = "2023-01-10", .after=3)

Resumen de diferencias

Característica bind_rows() add_row()
Tipo de operación Combina data frames Agrega filas individuales a un dataframe
Cantidad de filas Varias filas de varios dataframe Una o pocas filas a la vez
Columnas nuevas Sí, rellena con NA si no existen Deben existir en el tibble
Uso típico Unión de datasets Añadir manualmente datos

TRABAJANDO CON GRUPOS DE CASOS (Función group_by())

La función group_by() agrupa un conjunto de filas seleccionado en un conjunto de filas de resumen de acuerdo con los valores de una o más columnas o expresiones.

Podemos usar el siguiente código para agrupar el dataframe por el valor en la columna barrio a continuación, filtrar todos casos que tengan más de 18 elementos (en este caso, filas)

zonas_verdes %>%
  group_by(barrio) %>%
  filter(n() > 18)

Filtrar el parque más grande de cada barrio

zonas_verdes %>%
  group_by(barrio) %>%
  filter(superficie == max(superficie))

Filtrar los barrios que no tienen obras en los parques.

zonas_verdes %>%
  group_by(barrio) %>%
  filter(any(obras == 0))

Filtrar parques cuya superficie está por encima del promedio del barrio

zonas_verdes %>%
  group_by(barrio) %>%
  filter(superficie > mean(superficie, na.rm = TRUE))

Filtrar parques inaugurados antes del promedio de año del barrio

library(lubridate)
zonas_verdes %>%
  mutate(año = year(fecha_inauguracion)) %>%
  group_by(barrio) %>%
  filter(año < 1952)

Filtrar el parque más pequeño de cada barrio con obras

zonas_verdes %>%
  filter(obras == 1) %>%
  group_by(barrio) %>%
  filter(superficie == min(superficie))

Seleccionar los tres parques más grandes en cada barrio que tengan obras.

zonas_verdes %>%
  filter(obras == 1) %>%
  group_by(barrio) %>%
  slice_max(superficie, n = 3)
zonas_verdes %>%
  group_by(barrio) %>%
  filter(any(superficie >= 10))

UNGROUP()

Puede usar la función ungroup() en dplyr para desagrupar filas después de usar la función group_by() para resumir una variable por grupo.

FUNCIÓN Summarise()

La función summarize() (o su sinónimo summarise()) se usa para resumir un conjunto de datos, generalmente después de agruparlo con group_by(). La función summarise() funciona de forma análoga a la función mutate, excepto que en lugar de añadir nuevas columnas crea un nuevo data frame.

Por ejemplo, para el cálculo del promedio de superficie de los parques por barrio…

… el total de parques por barrio

zonas_verdes %>%
  group_by(barrio) %>%
  summarise(total_parques = sum(parques))

… el número de observaciones (filas) por barrio

zonas_verdes %>%
  group_by(barrio) %>%
  summarise(n = n())

… el promedio y desviación estándar de la superficie por barrio

zonas_verdes %>%
  group_by(barrio) %>%
  summarise(
    media_superficie = mean(superficie, na.rm = TRUE),
    sd_superficie = sd(superficie, na.rm = TRUE))

… o calcular la superficie total y promedio, ordenando resultados

zonas_verdes %>%
  group_by(barrio) %>%
  summarise(
    total_superficie = sum(superficie, na.rm = TRUE),
    promedio_superficie = mean(superficie, na.rm = TRUE)) %>%
  arrange(desc(total_superficie))

… o calcular para cada barrio, cuántos parques tiene (n()), su superficie media, el año más reciente de inauguración, y el porcentaje de parques con obras (mean(obras == 1)).

zonas_verdes %>%
  group_by(barrio) %>%
  summarise(
    n_parques = n(),
    superficie_media = mean(superficie, na.rm = TRUE),
    anio_reciente = max(lubridate::year(fecha_inauguracion), na.rm = TRUE),
    pct_con_obras = mean(obras == 1) * 100)

También se pueden conocer algunos estadísticos, como la mediana y la varianza de la variable superficie:

##    mediana variance
## 1 7.327894 7.084933

A continuación se muestran funciones que trabajando conjuntamente con la función summarise() facilitarán nuestro trabajo diario. Las primeras pertenecen al paquete base y las otras son del paquete dplyr. Todas ellas toman como argumento un vector y devuelven un único resultado.

Función Resultado
min(), max() Valores max y min
mean() media
median() mediana
sum() suma de los valores
var, sd() varianza y desviación típica
first() primer valor en un vector
last() el último valor en un vector
n() el número de valores en un vector
n_distinct() el número de valores distintos en un vector
nth() Extrer el valor que ocupa la posición n en un vector

SLICE

Puede usar la función slice() para subconjuntos de filas en función de sus ubicaciones. Ofrece diversas opciones, como seleccionar un subconjunto de una fila específica (la fila 3)…

zonas_verdes %>% 
  slice(3)

… un subconjunto de varias filas (filas 2, 5 y 6)

zonas_verdes %>% 
  slice(2, 5, 6)

… un subconjunto de un rango de filas (filas 1 a 3)

zonas_verdes %>% 
  slice(1:3)

… un subconjunto de filas por grupo (mostrar la primera fila de barrios)

zonas_verdes %>%
  group_by(barrio) %>%
  slice(1)

Selección de filas con el valor máximo ó mínimo. Función slice_max()

La función slice_max() usa la siguiente sintaxis:

slice_max(.data, order_by, n, …)

en la que:

  • .data: nombre del dataframe.
  • order_by: columna o conjunto de columnas a ordenar.
  • n: número de filas a seleccionar.

Por ejemplo, nos interesa conocer el valor más alto de la variable superficie.

zonas_verdes %>% 
  slice_max(superficie)

Si no especifica un valor para el argumento n, la función slice_max() devolverá por defecto la(s) fila(s) con el valor más grande en una variable concreta. Si se especifica un valor de n, devuelva las n filas con el valor más alto de esa variable.

zonas_verdes %>% 
  slice_max(superficie, n=5)

En caso de empates, se devuelven ambas filas.

zonas_verdes %>% 
  slice_max(parques, n = 4)

Su función contraria es slice_min(), cuya sintaxis es exactamente igual a la de slice_max(). Por ejemplo, nos interesa conocer el valor más bajo de la variable parques.

zonas_verdes %>% 
  slice_min(parques)

Si se especifica un valor de n, devuelva las n filas con el valor más bajo de esa variable.

zonas_verdes %>% 
  slice_min(parques, n=5)

EXTRACCIÓN DE FILAS DE MANERA ALEATORIA: función sample_n()

Para seleccionar una muestra aleatoria de filas por grupo se utiliza la función sample_n() junto con la función group_by(). La función sample_n() utiliza la siguiente sintaxis básica:

sample_n(tbl, size, replace=FALSE, …)

donde:

Supongamos que queremos seleccionar aleatoriamente 3 elementos de cada barrio, si bien podemos especificar un valor diferente para el argumento size para devolver un número diferente de elementos

zonas_verdes %>%
  group_by(barrio) %>%
  sample_n(size=3)

Tenga en cuenta que cada vez que ejecutamos este código, las filas seleccionadas para cada grupo pueden ser diferentes, ya que la función sample_n() selecciona las filas al azar. Si desea que el código sea reproducible, puede utilizar la función set.seed() para establecer una «semilla» aleatoria que nos permita seleccionar las mismas filas aleatorias cada vez.

set.seed(1)
zonas_verdes %>%
  group_by(barrio) %>%
  sample_n(size=2)

📝 ACTIVIDAD EN EL AULA :

Se volverá a trabajar con el fichero denominado inmobiliaria. Lo importaremos desde el servidor de la UC, si no disponemos de él.

url <- "https://personales.unican.es/rasillad/docencia/g2040/tema_1/datos/inmobiliaria.csv"
inmobiliaria <- read.csv(url, sep = ";", dec = ",", header = TRUE)
head(inmobiliaria)
##        barrio precio superficie accesibilidad habitaciones banos terraza garaje
## 1 San Agustin 173800       43.2             2            3     1       2      2
## 2 San Agustin 204600       56.9             1            2     1       2      2
## 3 San Agustin 220000       66.4             2            2     2       1      1
## 4 San Agustin 270600       61.9             1            3     2       2      2
## 5 San Agustin 297000       89.8             3            3     2       2      1
## 6 San Agustin 308000       71.0             2            3     2       1      2
##   comunidad   valor disponible
## 1     110.0 32830.6          1
## 2     151.8 59400.0          2
## 3       0.0 34624.5          1
## 4     286.0 59400.0          1
## 5       0.0 87047.4          2
## 6     264.0 68520.1          2

Cambiamos el nombre de la variable banos por baños.

inmobiliaria <- inmobiliaria %>% 
  rename(baños = banos)
  1. Filtramos el barrio “San Agustin” y ordenamos las viviendas por precio.
inmobiliaria %>%
  filter(barrio == "San Agustin") %>%
  arrange(precio)
  1. Identificamos barrios distintos y se ordenan alfabéticamente.
inmobiliaria %>%
  distinct(barrio) %>%
  arrange(barrio)
  1. Filtramos aquellas viviendas con una superficie superior a 60 m2 y obtenemos una muestra aleatoria de 3 viviendas.
inmobiliaria %>%
  filter(superficie > 60) %>%
  sample_n(3)
  1. Extraemos las 5 primeras filas y ordenamos por su valor (descendente)
inmobiliaria %>%
  slice(1:5) %>%
  arrange(desc(valor))
  1. Filtramos las viviendas disponibles (valor 1) y seleccionamos qué barrios (únicos) poseen estas viviendas.
inmobiliaria %>%
  filter(disponible == 1) %>%
  distinct(barrio)
  1. Se añade una nueva fila con los mismos atributos y se ordena todo el dataframe por habitaciones.
inmobiliaria %>%
  add_row(barrio = "Centro", precio = 250000, superficie = 70,
          accesibilidad = 2, habitaciones = 2, baños = 1,
          terraza = 1, garaje = 1, comunidad = 100, valor = 50000, disponible = 1) %>%
  arrange(habitaciones)
  1. Creamos un nuevo dataframe, lo unimos a inmobiliaria y ordenamos el nuevo conjunto por el precio de las viviendas.
nuevos <- data.frame(
  barrio = "Nuevo Barrio", precio = 180000, superficie = 60,
  accesibilidad = 1, habitaciones = 2, baños = 1,
  terraza = 1, garaje = 1, comunidad = 90, valor = 40000, disponible = 1
)
inmobiliaria %>%
  bind_rows(nuevos) %>%
  arrange(precio)
  1. Filtramos, ordenamos y extraemos 3 filas.
inmobiliaria %>%
  filter(barrio == "El Robledo") %>%
  arrange(desc(precio)) %>%
  slice(1:3)
  1. Obtenemos barrios únicos tras añadir una fila extra
inmobiliaria %>%
  add_row(barrio = "San Pedro", precio = 220000, superficie = 65,
          accesibilidad = 2, habitaciones = 3, baños = 2,
          terraza = 1, garaje = 1, comunidad = 120, valor = 60000, disponible = 2) %>%
  distinct(barrio)
  1. Filtramos las viviendas con igual o más de 2 baños, extraemos una muestra aleatoria de 5 viviendas y ordenamos por precio (descendente)
inmobiliaria %>%
  filter(baños >= 2) %>%
  sample_n(5) %>%
  arrange(desc(precio))