IV Meetup ML, Belo Horizonte
Neste tutorial você aprenderá a usar o R
e suas principais bibliotecas de manipulação e visualização de dados para fazer Análise Exploratória de Dados (EDA - Exploratory Data Analysis).
Base de Dados NYC Flights
A base de Dados NYC Flights contém informações sobre todos os vôos que saíram da cidade de Nova Iorque no ano de 2014: 253316 vôos no total.
library("readr")
## Import dataset
flights <- readr::read_csv("./data/flights.csv.zip")
head(flights, n = 5)
dep_time | dep_delay | arr_time | arr_delay | origin | dest | distance |
---|---|---|---|---|---|---|
2014-01-01 09:14:00 | 14 | 2014-01-01 15:13:00 | 13 | JFK | LAX | 2475 |
2014-01-01 11:57:00 | -3 | 2014-01-01 18:00:00 | 13 | JFK | LAX | 2475 |
2014-01-01 19:02:00 | 2 | 2014-01-02 00:53:00 | 9 | JFK | LAX | 2475 |
2014-01-01 07:22:00 | -8 | 2014-01-01 09:59:00 | -26 | LGA | PBI | 1035 |
2014-01-01 13:47:00 | 2 | 2014-01-01 19:37:00 | 1 | JFK | LAX | 2475 |
A base de dados disponibiliza as seguintes informações: horário de saída e chegada dos vôos, atraso em minutos na saída e na chegada dos vôos, aeroportos de origem e destino, e distância total percorrida em Km. Quais insights podemos extrair desses dados?
A melhor forma de começar consiste em enumerar as perguntas que queremos responder. Exemplo:
- Quantos vôos saíram atrasados de cada aeroporto?
- O aeroporto de destino tem influência nos atrasos de saída dos vôos?
- Qual o aeroporto mais disciplinado com os horários de saída?
- O tempo de atraso na chegada está correlacionado com o tempo de atraso na saída?
- O tempo de atraso na chegada está correlacionado com a distância do vôo?
- Qual o comportamento dos atrasos ao longo dos meses do ano?
- Existe algum dia da semana ou horário do dia com maior incidência de atrasos para determinado aeroporto?
Atraso médio dos vôos saindo de EWR
Vamos utilizar o pacote dplyr
para manipulação dos dados. Resolveremos problemas complexos quebrando em pequenas partes. Com os verbos básicos do dplyr
conseguiremos fazer diversos tipos de análise:
filter()
eselect()
: seleção de subconjuntos da base de dadosmutate()
: tranformação de variáveis e criação de novassummarize()
: simplificação das variáveis em estatísticas resumoarrange()
: reordenação dos dadosjoin()
: união de diferentes bases de dados
Geralmente, uma análise será composta por uma combinação sequencial de chamadas aos métodos do dplyr
. Existem 3 maneiras distintas de combinar as chamadas.
Chamada usando variáveis intermediárias
É uma maneira confusa, pois é muito difícil atribuir nomes coerentes a cada uma das variáveis intermediárias.
library("dplyr")
delays1 <- dplyr::group_by(flights, origin, dest)
delays2 <- dplyr::summarise(delays1, delay = mean(dep_delay), num = n())
delays3 <- dplyr::filter(delays2, origin == "EWR")
delays4 <- dplyr::top_n(delays3, n = 10, wt = delay)
delays <- dplyr::arrange(delays4, desc(delay))
head(delays, n = 5)
origin | dest | delay | count |
---|---|---|---|
EWR | AVP | 83.00000 | 1 |
EWR | CAE | 36.09524 | 21 |
EWR | TYS | 32.81513 | 238 |
EWR | TUL | 28.89552 | 201 |
EWR | SAT | 26.71220 | 205 |
Chamada aninhada
É sem dúvidas a maneira mais difícil de ler o código.
delays <- dplyr::arrange(
dplyr::top_n(
dplyr::filter(
dplyr::summarise(
dplyr::group_by(
flights, origin, dest
), delay = mean(dep_delay), num = n()
), origin == "EWR"
), n = 10, wt = delay
), desc(delay)
)
head(delays, n = 5)
origin | dest | delay | count |
---|---|---|---|
EWR | AVP | 83.00000 | 1 |
EWR | CAE | 36.09524 | 21 |
EWR | TYS | 32.81513 | 238 |
EWR | TUL | 28.89552 | 201 |
EWR | SAT | 26.71220 | 205 |
Chamada usando operador de pipeline
É a maneira mais limpa de ler a sequência de chamadas e não requer nomes intermediários. Para isso usamos o operador %>%
(pipe operator). A análise com dplyr
é de fato um pipeline de processamento dos dados.
library("magrittr")
delays <- flights %>%
dplyr::group_by(origin, dest) %>%
dplyr::summarise(delay = mean(dep_delay), num = n()) %>%
dplyr::filter(origin == "EWR") %>%
dplyr::top_n(n = 10, wt = delay) %>%
dplyr::arrange(desc(delay))
head(delays, n = 5)
origin | dest | delay | count |
---|---|---|---|
EWR | AVP | 83.00000 | 1 |
EWR | CAE | 36.09524 | 21 |
EWR | TYS | 32.81513 | 238 |
EWR | TUL | 28.89552 | 201 |
EWR | SAT | 26.71220 | 205 |
Visualização dos atrasos em EWR
Para visualização dos dados utilizaremos o pacote ggplot2
, que segue os conceitos contidos no livro The Grammar of Graphics. Os gráficos são construídos também em etapas, adicionando camadas (layers). Fica mais fácil de entender exemplificando:
library("tidyr")
library("ggplot2")
library("viridis")
dt <- delays %>%
dplyr::mutate(delay = round(delay, 2)) %>%
tidyr::unite(col = "flight", origin, dest, sep = "-")
g <- ggplot(aes(x = flight, y = delay), data = dt) +
geom_bar(stat = "identity") +
labs(x = "Flight", y = "Delay (min)", title = "Average Delays",
subtitle = "Flights from EWR")
plot(g)
Podemos deixar esse gráfico de barras muito mais apresentável:
g <- ggplot(aes(x = reorder(flight, delay), y = delay,
fill = reorder(flight, delay)), data = dt) +
geom_bar(width = 0.7, stat = "identity", color = "white", size = 0.5) +
coord_flip() +
geom_text(aes(label = delay), nudge_y = -4, color = "white", size = 4,
fontface = "bold") +
viridis::scale_fill_viridis(direction = -1, discrete = TRUE, guide = "none") +
labs(x = "Flight", y = "Delay (min)", title = "Average Delays",
subtitle = "Flights from EWR")
plot(g)
Distribuição dos atrasos
A análise anterior nos deu uma ideia da média dos atrasos. Mas como esses atrasos variam em torno da média? Para responder a essa pergunta, vamos utilizar um recusos gráfico conhecido como boxplot, que resume a tendência central e a dispersão dos dados.
Primeiramente, vamos escolher os 10 destinos mais frequentes saindo de EWR
e remover os valores extremos dos atrasos, para podermos visualizar melhor os dados:
dt <- flights %>%
dplyr::filter(origin == "EWR") %>%
dplyr::group_by(dest) %>%
dplyr::summarise(num = n()) %>%
dplyr::top_n(n = 10, wt = num)
dt <- flights %>%
dplyr::filter(origin == "EWR", dest %in% dt$dest) %>%
dplyr::filter(dep_delay > 0) %>%
dplyr::filter(dep_delay > quantile(dep_delay, 0.1),
dep_delay < quantile(dep_delay, 0.9)) %>%
dplyr::select(origin, dest, dep_delay)
Agora nosso boxplot:
g <- ggplot(aes(x = dest, y = dep_delay), data = dt) +
geom_boxplot(outlier.shape = NA) +
labs(x = "Destination", y = "Delay (min)", title = "Distribution of Delays",
subtitle = "Flights from EWR")
plot(g)
Assim como fizemos para o gráfico de barras, vamos tornar o boxplot mais apresentável:
g <- ggplot(aes(x = dest, y = dep_delay, fill = dest), data = dt) +
geom_jitter(width = 0.2, color = "gray40", alpha = 0.6, size = 1) +
geom_boxplot(outlier.shape = NA, alpha = 0.6, size = 0.6) +
viridis::scale_fill_viridis(discrete = TRUE, guide = "none") +
viridis::scale_color_viridis(discrete = TRUE, guide = "none") +
labs(x = "Destination", y = "Delay (min)", title = "Distribution of Delays",
subtitle = "Flights from EWR")
plot(g)
Acesse o site R Graph Gallery e busque inspiração por meio de vários exemplos de visualização de dados usando ggplot2
e alguns outros recursos gráficos do R
.
Atraso médio do vôo JFK-LAX ao longo do ano
Vamos agora utilizar o pacote lubridate
para extrair informação das variáveis de data. Escolhemos o vôo JFK-LAX
para exemplificar, pois ele é o mais frequente:
dt <- flights %>%
dplyr::filter(dep_delay > 0) %>%
tidyr::unite("flight", origin, dest, sep = "-") %>%
dplyr::group_by(flight) %>%
dplyr::summarise(num = n()) %>%
dplyr::ungroup() %>%
dplyr::arrange(desc(num))
head(dt, n = 5)
flight | num |
---|---|
JFK-LAX | 3489 |
LGA-ATL | 2752 |
JFK-SFO | 2733 |
LGA-ORD | 2614 |
EWR-SFO | 2559 |
Vamos criar a variável dep_month
, contendo o mês do embarque de cada vôo:
dt <- flights %>%
na.omit() %>%
dplyr::filter(dep_delay > 0) %>%
tidyr::unite("flight", origin, dest, sep = "-") %>%
dplyr::filter(flight == "JFK-LAX") %>%
dplyr::mutate(dep_month = lubridate::month(dep_time)) %>%
dplyr::group_by(dep_month) %>%
dplyr::summarize(delay_mean = median(dep_delay), delay_sd = IQR(dep_delay),
count = n()) %>%
dplyr::ungroup()
Agora vamos plotar o atraso médio ao longo do ano:
g <- ggplot(aes(x = dep_month, y = delay_mean), data = dt) +
geom_ribbon(aes(ymin = delay_mean - delay_sd, ymax = delay_mean + delay_sd),
alpha = 0.2) +
geom_line(size = 1, color = "#482878FF", alpha = 0.6) +
geom_point(size = 3, color = "#482878FF", alpha = 0.8) +
labs(x = "Month", y = "Delay (min)", title = "Delay Trends",
subtitle = "Flight from JFK to LAX") +
theme_bw()
plot(g)
Número de atrasos por dia da semana
Será que o dia da semana tem influência nos atrasos? Vamos utilizar um outro tipo de recurso gráfico, chamado heatmap, para visualizar a taxa de atrasos por dia da semana ao longo do ano. Vamos escolher novamente o vôo JFK-LAX
para exemplificar.
dt <- flights %>%
na.omit() %>%
tidyr::unite("flight", origin, dest, sep = "-") %>%
dplyr::filter(flight == "JFK-LAX") %>%
dplyr::mutate(dep_weekday = lubridate::wday(dep_time, label = TRUE,
abbr = TRUE)) %>%
dplyr::mutate(dep_month = lubridate::month(dep_time, label = TRUE,
abbr = TRUE)) %>%
dplyr::group_by(dep_month, dep_weekday) %>%
dplyr::summarise(rate = sum(dep_delay > 0) / n()) %>%
dplyr::ungroup()
head(dt, n = 5)
dep_month | dep_weekday | rate |
---|---|---|
1 | Dom | 0.5877193 |
1 | Seg | 0.3865546 |
1 | Ter | 0.4363636 |
1 | Qua | 0.6056338 |
1 | Qui | 0.5751634 |
O heatmap permite visualizar 3 variáveis usando apenas 2 dimensões:
g <- ggplot(dt, aes(x = dep_month, y = dep_weekday, fill = rate)) +
geom_tile(color = "gray93", size = 1, alpha = 0.8) +
geom_text(aes(label = round(rate, 2)), color = "white") +
viridis::scale_fill_viridis(direction = -1, name = "Rate") +
guides(fill = guide_colorbar(barwidth = 0.5, barheight = 10)) +
theme_gray(base_family = "Helvetica", base_size = 14) +
labs(x = "Month", y = "Weekday")
plot(g)
Localização dos Aeroportos
Vamos combinar os dados dos vôos com uma nova base de dados contendo informações detalhadas de cada aeroporto.
airports <- readr::read_csv("./data/airports.csv.zip")
head(airports, n = 5)
faa | name | lat | lon | alt | tz | dst | tzone |
---|---|---|---|---|---|---|---|
04G | Lansdowne Airport | 41.13047 | -80.61958 | 1044 | -5 | A | America/New_York |
06A | Moton Field Municipal Airport | 32.46057 | -85.68003 | 264 | -6 | A | America/Chicago |
06C | Schaumburg Regional | 41.98934 | -88.10124 | 801 | -6 | A | America/Chicago |
06N | Randall Airport | 41.43191 | -74.39156 | 523 | -5 | A | America/New_York |
09J | Jekyll Island Airport | 31.07447 | -81.42778 | 11 | -5 | A | America/New_York |
O pacote ggmap
permite inserir mapas como camadas do ggplot2
. Vamos utilizá-lo para plotar a localização dos aeroportos de destino:
library("ggmap")
dest <- airports %>%
dplyr::filter(faa %in% unique(flights$dest))
map <- ggmap::get_map("United States", zoom = 4, maptype = "terrain",
scale = 2, color = "bw")
g <- ggmap::ggmap(map, extent = "device", darken = 0.1) +
stat_density2d(data = dest, aes(x = lon, y = lat, fill = ..level..,
alpha = ..level..),
size = 0.01, bins = 10, geom = "polygon") +
geom_density2d(data = dest, aes(x = lon, y = lat, color = ..level..),
size = 0.5, bins = 15, alpha = 0.8) +
geom_point(aes(lon, lat), data = dest, size = 2, alpha = 0.8, color = "black") +
scale_color_gradientn(colors = heat.colors(100)[80:5], guide = "none") +
scale_fill_gradientn(colors = heat.colors(100)[80:5], guide = "none") +
scale_alpha(range = c(0.1, 0.4), guide = "none")
plot(g)
Agora, vamos plotar os trajetos dos vôos que saíram de EWR:
dt <- flights %>%
dplyr::filter(origin == "EWR") %>%
dplyr::rename(faa = dest) %>%
dplyr::left_join(airports, by = "faa") %>%
na.omit() %>%
dplyr::rename(dest = faa, dest_lat = lat, dest_lon = lon)
dt <- dt %>%
dplyr::select(origin, dest, dest_lat, dest_lon, dep_delay) %>%
dplyr::rename(faa = origin) %>%
dplyr::left_join(airports, by = "faa") %>%
na.omit() %>%
dplyr::rename(origin = faa, orig_lat = lat, orig_lon = lon) %>%
dplyr::select(starts_with("orig"), starts_with("dest"), dep_delay)
dt <- dt %>%
dplyr::filter(dep_delay > 0) %>%
dplyr::group_by(origin, dest) %>%
dplyr::summarise_each(funs(median))
map <- ggmap::get_map("United States", zoom = 4, maptype = "terrain",
scale = 2, color = "color")
g <- ggmap::ggmap(map, extent = "device", darken = 0.5) +
geom_curve(aes(x = orig_lon, y = orig_lat, xend = dest_lon, yend = dest_lat,
color = dep_delay), data = dt, size = 0.5, curvature = 0.5) +
geom_point(aes(x = dest_lon, y = dest_lat), data = dt, color = "red") +
geom_text(data = dt, aes(x = dest_lon, y = dest_lat, label = dest), col = "black",
size = 3, nudge_x = .5, nudge_y = .5, fontface = "bold") +
coord_cartesian() +
viridis::scale_color_viridis(name = "Avg Delay", option = "B", direction = -1) +
guides(color = guide_colorbar(barwidth = 15, barheight = 0.5)) +
theme(legend.position = "bottom")
plot(g)
Como usar as ferramentas em produção?
O pacote dplyr
, além de data.frames, aceita como entrada tabelas de diferentes bancos relacionais. Exemplo:
db <- dplyr::src_postgres(
dbname = Sys.getenv("POSTGRES_DBNAME"),
port = Sys.getenv("POSTGRES_PORT"),
host = Sys.getenv("POSTGRES_HOST"),
user = Sys.getenv("POSTGRES_USER"),
password = Sys.getenv("POSTGRES_PASS")
)
my.tbl <- dplyr::tbl(db, "tbl_name")
my.tbl <- dplyr::tbl(db, dplyr::sql("SELECT * FROM tbl_name LIMIT 50"))
dt <- my.tbl %>%
dplyr::filter() %>%
dplyr::mutate() %>%
dplyr::select() %>%
dplyr::collect()
É possível também manipular Spark data frames usando dplyr
, por meio do pacote sparklyr. Exemplo:
library("sparklyr")
library("dplyr")
sc <- spark_connect(master="local")
flights <- copy_to(sc, flights, "flights")
dt <- flights %>%
dplyr::filter() %>%
dplyr::group_by() %>%
dplyr::summarise() %>%
dplyr::collect()