Neste contexto, uma das tarefas mais consumidoras de tempo é a preparação dos dados
Esta tarefa inclui a recolha dos dados, a sua restruturação para um formato adequado, a exploração de padrões através da visualização preliminar dos dados (o que poderíamos designar por análise de dados exploratória)
É importante usar ferramentas que nos permitam ter ganhos de eficiência nestes passos, antes de passarmos à importante fase da modelação
O R possui várias bibliotecas adequadas a cada um destes passos (como observámos na importação da dados)
Vamos agora discutir uma biblioteca muito útil na restruturação/manipulação de dados
O pacote dplyr
é utilizado para manipular data frames de forma rápida e conveniente.
library(dplyr)
library(MASS)
my.data <- tbl_df(Boston[,1:10]) # converte o data frame num formato similar
my.data
# A tibble: 506 x 10
crim zn indus chas nox rm age dis rad tax
* <dbl> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <dbl> <int> <dbl>
1 0.00632 18.0 2.31 0 0.538 6.575 65.2 4.0900 1 296
2 0.02731 0.0 7.07 0 0.469 6.421 78.9 4.9671 2 242
3 0.02729 0.0 7.07 0 0.469 7.185 61.1 4.9671 2 242
4 0.03237 0.0 2.18 0 0.458 6.998 45.8 6.0622 3 222
5 0.06905 0.0 2.18 0 0.458 7.147 54.2 6.0622 3 222
6 0.02985 0.0 2.18 0 0.458 6.430 58.7 6.0622 3 222
7 0.08829 12.5 7.87 0 0.524 6.012 66.6 5.5605 5 311
8 0.14455 12.5 7.87 0 0.524 6.172 96.1 5.9505 5 311
9 0.21124 12.5 7.87 0 0.524 5.631 100.0 6.0821 5 311
10 0.17004 12.5 7.87 0 0.524 6.004 85.9 6.5921 5 311
# ... with 496 more rows
No dplyr
verbos são acções de manipulação. Um dos verbos designa-se filter
e seleciona linhas segundo o critério dado:
dplyr::filter(my.data, rad==4)
# A tibble: 110 x 10
crim zn indus chas nox rm age dis rad tax
<dbl> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <dbl> <int> <dbl>
1 0.62976 0 8.14 0 0.538 5.949 61.8 4.7075 4 307
2 0.63796 0 8.14 0 0.538 6.096 84.5 4.4619 4 307
3 0.62739 0 8.14 0 0.538 5.834 56.5 4.4986 4 307
4 1.05393 0 8.14 0 0.538 5.935 29.3 4.4986 4 307
5 0.78420 0 8.14 0 0.538 5.990 81.7 4.2579 4 307
6 0.80271 0 8.14 0 0.538 5.456 36.6 3.7965 4 307
7 0.72580 0 8.14 0 0.538 5.727 69.5 3.7965 4 307
8 1.25179 0 8.14 0 0.538 5.570 98.1 3.7979 4 307
9 0.85204 0 8.14 0 0.538 5.965 89.2 4.0123 4 307
10 1.23247 0 8.14 0 0.538 6.142 91.7 3.9769 4 307
# ... with 100 more rows
Vamos utilizar o operador %>%
que passa o fluxo de dados para a instrução seguinte.
O código anterior passa a ser escrito assim:
my.data %>% dplyr::filter(rad==4)
# A tibble: 110 x 10
crim zn indus chas nox rm age dis rad tax
<dbl> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <dbl> <int> <dbl>
1 0.62976 0 8.14 0 0.538 5.949 61.8 4.7075 4 307
2 0.63796 0 8.14 0 0.538 6.096 84.5 4.4619 4 307
3 0.62739 0 8.14 0 0.538 5.834 56.5 4.4986 4 307
4 1.05393 0 8.14 0 0.538 5.935 29.3 4.4986 4 307
5 0.78420 0 8.14 0 0.538 5.990 81.7 4.2579 4 307
6 0.80271 0 8.14 0 0.538 5.456 36.6 3.7965 4 307
7 0.72580 0 8.14 0 0.538 5.727 69.5 3.7965 4 307
8 1.25179 0 8.14 0 0.538 5.570 98.1 3.7979 4 307
9 0.85204 0 8.14 0 0.538 5.965 89.2 4.0123 4 307
10 1.23247 0 8.14 0 0.538 6.142 91.7 3.9769 4 307
# ... with 100 more rows
Alguns exemplos de uso do operador %>%
:
1:10 %>% sum() # igual a sum(1:10)
[1] 55
1:10 %>% cumsum()
[1] 1 3 6 10 15 21 28 36 45 55
1:10 %>% cumsum() %>% diff()
[1] 2 3 4 5 6 7 8 9 10
1:10 %>% cumsum() %>% diff() %>% sum()
[1] 54
c(2,2,2) %>% `^`(3:5) # igual a 2^3, 2^4, 2^5
[1] 8 16 32
c(2,2,2) %>% `^`(3:5, .) # igual a 3^2, 4^2, 5^2
[1] 9 16 25
3:5 %>% `*`(.,.) # igual a 3*3, 4*4, 5*5
[1] 9 16 25
Podemos ordenar os dados por colunas:
my.data %>% arrange(rad, desc(tax), age)
# A tibble: 506 x 10
crim zn indus chas nox rm age dis rad tax
<dbl> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <dbl> <int> <dbl>
1 0.02498 0 1.89 0 0.5180 6.540 59.7 6.2669 1 422
2 0.02899 40 1.25 0 0.4290 6.939 34.5 8.7921 1 335
3 0.06211 40 1.25 0 0.4290 6.490 44.4 8.7921 1 335
4 0.03548 80 3.64 0 0.3920 5.876 19.1 9.2203 1 315
5 0.04819 80 3.64 0 0.3920 6.108 32.0 9.2203 1 315
6 0.03466 35 6.06 0 0.4379 6.031 23.3 6.6407 1 304
7 0.05023 35 6.06 0 0.4379 5.706 28.4 6.6407 1 304
8 0.01096 55 2.25 0 0.3890 6.453 31.9 7.3073 1 300
9 0.00632 18 2.31 0 0.5380 6.575 65.2 4.0900 1 296
10 0.00906 90 2.97 0 0.4000 7.088 20.8 7.3073 1 285
# ... with 496 more rows
Podemos escolher apenas algumas das colunas disponíveis:
my.data %>% dplyr::select(rad, age) # o pacote MASS também tem uma funcão select
# A tibble: 506 x 2
rad age
* <int> <dbl>
1 1 65.2
2 2 78.9
3 2 61.1
4 3 45.8
5 3 54.2
6 3 58.7
7 5 66.6
8 5 96.1
9 5 100.0
10 5 85.9
# ... with 496 more rows
Podemos escolher um intervalo de colunas:
my.data %>% dplyr::select(nox:dis)
# A tibble: 506 x 4
nox rm age dis
* <dbl> <dbl> <dbl> <dbl>
1 0.538 6.575 65.2 4.0900
2 0.469 6.421 78.9 4.9671
3 0.469 7.185 61.1 4.9671
4 0.458 6.998 45.8 6.0622
5 0.458 7.147 54.2 6.0622
6 0.458 6.430 58.7 6.0622
7 0.524 6.012 66.6 5.5605
8 0.524 6.172 96.1 5.9505
9 0.524 5.631 100.0 6.0821
10 0.524 6.004 85.9 6.5921
# ... with 496 more rows
Ou até usar critérios mais complexos:
my.data %>% dplyr::select(-matches('^[cr]')) # não começa por c ou r
# A tibble: 506 x 6
zn indus nox age dis tax
* <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 18.0 2.31 0.538 65.2 4.0900 296
2 0.0 7.07 0.469 78.9 4.9671 242
3 0.0 7.07 0.469 61.1 4.9671 242
4 0.0 2.18 0.458 45.8 6.0622 222
5 0.0 2.18 0.458 54.2 6.0622 222
6 0.0 2.18 0.458 58.7 6.0622 222
7 12.5 7.87 0.524 66.6 5.5605 311
8 12.5 7.87 0.524 96.1 5.9505 311
9 12.5 7.87 0.524 100.0 6.0821 311
10 12.5 7.87 0.524 85.9 6.5921 311
# ... with 496 more rows
Adicionar novas colunas como função de outras:
my.data %>%
mutate(twice.age = 2*age,
new.col = sqrt(twice.age),
good.age = factor(ifelse(age>50,"good","bad"))) %>%
dplyr::select(age:good.age)
# A tibble: 506 x 7
age dis rad tax twice.age new.col good.age
<dbl> <dbl> <int> <dbl> <dbl> <dbl> <fctr>
1 65.2 4.0900 1 296 130.4 11.419282 good
2 78.9 4.9671 2 242 157.8 12.561847 good
3 61.1 4.9671 2 242 122.2 11.054411 good
4 45.8 6.0622 3 222 91.6 9.570789 bad
5 54.2 6.0622 3 222 108.4 10.411532 good
6 58.7 6.0622 3 222 117.4 10.835128 good
7 66.6 5.5605 5 311 133.2 11.541230 good
8 96.1 5.9505 5 311 192.2 13.863621 good
9 100.0 6.0821 5 311 200.0 14.142136 good
10 85.9 6.5921 5 311 171.8 13.107250 good
# ... with 496 more rows
Podemos fazer resumos dos dados:
my.data %>% summarise(mean.age = mean(age, na.rm = TRUE),
sd.age = sd(age, na.rm = TRUE))
# A tibble: 1 x 2
mean.age sd.age
<dbl> <dbl>
1 68.5749 28.14886
Este verbo permite selecionar linhas:
my.data %>% slice(c(2:6,10,n())) # n() representa a última linha
# A tibble: 7 x 10
crim zn indus chas nox rm age dis rad tax
<dbl> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <dbl> <int> <dbl>
1 0.02731 0.0 7.07 0 0.469 6.421 78.9 4.9671 2 242
2 0.02729 0.0 7.07 0 0.469 7.185 61.1 4.9671 2 242
3 0.03237 0.0 2.18 0 0.458 6.998 45.8 6.0622 3 222
4 0.06905 0.0 2.18 0 0.458 7.147 54.2 6.0622 3 222
5 0.02985 0.0 2.18 0 0.458 6.430 58.7 6.0622 3 222
6 0.17004 12.5 7.87 0 0.524 6.004 85.9 6.5921 5 311
7 0.04741 0.0 11.93 0 0.573 6.030 80.8 2.5050 1 273
Podemos ir buscar os valores únicos de uma dada coluna:
my.data %>%
dplyr::select(tax) %>% # selecionar as taxas
distinct(tax) %>% # escolher as distintas
arrange(tax) %>% # ordená-las
as.list() # transformar em lista
$tax
[1] 187 188 193 198 216 222 223 224 226 233 241 242 243 244 245 247 252
[18] 254 255 256 264 265 270 273 276 277 279 280 281 284 285 287 289 293
[35] 296 300 304 305 307 311 313 315 329 330 334 335 337 345 348 351 352
[52] 358 370 384 391 398 402 403 411 422 430 432 437 469 666 711
O pacote permite realizar amostragem de forma sucinta:
sample_n(my.data, 6) # amostra com 6 linhas
sample_frac(my.data, .03) # amostra com 3% dos dados
sample_frac(my.data, .03, replace=TRUE ) # amostra com bootstrap
sample_n(my.data[1:5,], size=10, weight=5:1, replace=TRUE) # amostragem ponderada
É possível dividir a tabela em grupos – dado um critério – de modo a repetir uma operação sobre os vários grupos:
my.data %>%
group_by(tax) %>% # agrupar por taxas iguais
summarise(count=n(), mean.age=mean(age, na.rm=TRUE))
# A tibble: 66 x 3
tax count mean.age
<dbl> <int> <dbl>
1 187 1 36.10000
2 188 7 89.07143
3 193 8 75.48750
4 198 1 24.80000
5 216 5 48.62000
6 222 7 57.15714
7 223 5 46.08000
8 224 10 42.33000
9 226 1 21.90000
10 233 9 40.65556
# ... with 56 more rows
Existem várias operações disponíveis:
my.data %>%
group_by(rad) %>%
summarise(n.taxes = n(),
diff.taxes = n_distinct(tax),
min.tax = min(tax),
median.tax = median(tax),
max.tax = max(tax))
# A tibble: 9 x 6
rad n.taxes diff.taxes min.tax median.tax max.tax
<int> <int> <int> <dbl> <dbl> <dbl>
1 1 20 12 198 284.5 422
2 2 24 7 188 270.0 348
3 3 38 11 193 233.0 469
4 4 110 21 224 306.0 711
5 5 115 16 187 358.0 403
6 6 26 4 293 391.0 432
7 7 17 3 222 330.0 330
8 8 24 2 284 307.0 307
9 24 132 1 666 666.0 666
É mesmo possível usar as nossas próprias funções:
my.mode <- function(x) {
ux <- unique(x)
ux[which.max(tabulate(match(x, ux)))]
}
my.data %>%
group_by(rad) %>%
summarise(mode = my.mode(tax))
# A tibble: 9 x 2
rad mode
<int> <dbl>
1 1 273
2 2 188
3 3 233
4 4 307
5 5 403
6 6 432
7 7 330
8 8 307
9 24 666
Os grupos podem ter subgrupos:
my.data %>%
group_by(tax, rad) %>% # cada grupo taxa tem subgrupos rad
summarise(size=n()) %>%
arrange(tax)
Source: local data frame [77 x 3]
Groups: tax [66]
tax rad size
<dbl> <int> <int>
1 187 5 1
2 188 2 7
3 193 3 8
4 198 1 1
5 216 3 1
6 216 5 4
7 222 3 3
8 222 7 4
9 223 3 5
10 224 4 2
# ... with 67 more rows
my.data %>%
group_by(tax, rad) %>%
summarise(size=n())
Source: local data frame [77 x 3]
Groups: tax [?]
tax rad size
<dbl> <int> <int>
1 187 5 1
2 188 2 7
3 193 3 8
4 198 1 1
5 216 3 1
6 216 5 4
7 222 3 3
8 222 7 4
9 223 3 5
10 224 4 2
# ... with 67 more rows
my.data %>%
group_by(tax, rad) %>%
summarise(size=n()) %>%
summarise(sum=sum(size))
# A tibble: 66 x 2
tax sum
<dbl> <int>
1 187 1
2 188 7
3 193 8
4 198 1
5 216 5
6 222 7
7 223 5
8 224 10
9 226 1
10 233 9
# ... with 56 more rows
Carregar a seguinte tabela do Bureau of Transportation Statistics:
library(nycflights13) # install.packages("nycflights13")
flights
# A tibble: 336,776 x 19
year month day dep_time sched_dep_time dep_delay arr_time
<int> <int> <int> <int> <int> <dbl> <int>
1 2013 1 1 517 515 2 830
2 2013 1 1 533 529 4 850
3 2013 1 1 542 540 2 923
4 2013 1 1 544 545 -1 1004
5 2013 1 1 554 600 -6 812
6 2013 1 1 554 558 -4 740
7 2013 1 1 555 600 -5 913
8 2013 1 1 557 600 -3 709
9 2013 1 1 557 600 -3 838
10 2013 1 1 558 600 -2 753
# ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
# arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
# origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
# minute <dbl>, time_hour <time>
Ver os voos do dia 1 de Janeiro:
# A tibble: 842 x 19
year month day dep_time sched_dep_time dep_delay arr_time
<int> <int> <int> <int> <int> <dbl> <int>
1 2013 1 1 517 515 2 830
2 2013 1 1 533 529 4 850
3 2013 1 1 542 540 2 923
4 2013 1 1 544 545 -1 1004
5 2013 1 1 554 600 -6 812
6 2013 1 1 554 558 -4 740
7 2013 1 1 555 600 -5 913
8 2013 1 1 557 600 -3 709
9 2013 1 1 557 600 -3 838
10 2013 1 1 558 600 -2 753
# ... with 832 more rows, and 12 more variables: sched_arr_time <int>,
# arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
# origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
# minute <dbl>, time_hour <time>
Ver os voos de Novembro e Dezembro (operador pertença é dado por %in%
):
# A tibble: 55,403 x 19
year month day dep_time sched_dep_time dep_delay arr_time
<int> <int> <int> <int> <int> <dbl> <int>
1 2013 11 1 5 2359 6 352
2 2013 11 1 35 2250 105 123
3 2013 11 1 455 500 -5 641
4 2013 11 1 539 545 -6 856
5 2013 11 1 542 545 -3 831
6 2013 11 1 549 600 -11 912
7 2013 11 1 550 600 -10 705
8 2013 11 1 554 600 -6 659
9 2013 11 1 554 600 -6 826
10 2013 11 1 554 600 -6 749
# ... with 55,393 more rows, and 12 more variables: sched_arr_time <int>,
# arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
# origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
# minute <dbl>, time_hour <time>
Ver os voos que não partiram ou chegaram atrasados mais que 15 minutos (operadores lógicos dados por !
, &
e |
):
# A tibble: 235,822 x 19
year month day dep_time sched_dep_time dep_delay arr_time
<int> <int> <int> <int> <int> <dbl> <int>
1 2013 1 1 517 515 2 830
2 2013 1 1 544 545 -1 1004
3 2013 1 1 554 600 -6 812
4 2013 1 1 554 558 -4 740
5 2013 1 1 557 600 -3 709
6 2013 1 1 557 600 -3 838
7 2013 1 1 558 600 -2 753
8 2013 1 1 558 600 -2 849
9 2013 1 1 558 600 -2 853
10 2013 1 1 558 600 -2 924
# ... with 235,812 more rows, and 12 more variables: sched_arr_time <int>,
# arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
# origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
# minute <dbl>, time_hour <time>
Que voos não possuem a ordem de partida? Usar a função is.na()
.
# A tibble: 8,255 x 19
year month day dep_time sched_dep_time dep_delay arr_time
<int> <int> <int> <int> <int> <dbl> <int>
1 2013 1 1 NA 1630 NA NA
2 2013 1 1 NA 1935 NA NA
3 2013 1 1 NA 1500 NA NA
4 2013 1 1 NA 600 NA NA
5 2013 1 2 NA 1540 NA NA
6 2013 1 2 NA 1620 NA NA
7 2013 1 2 NA 1355 NA NA
8 2013 1 2 NA 1420 NA NA
9 2013 1 2 NA 1321 NA NA
10 2013 1 2 NA 1545 NA NA
# ... with 8,245 more rows, and 12 more variables: sched_arr_time <int>,
# arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
# origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
# minute <dbl>, time_hour <time>
Ordenar decrescentemente por atraso à chegada:
# A tibble: 336,776 x 19
year month day dep_time sched_dep_time dep_delay arr_time
<int> <int> <int> <int> <int> <dbl> <int>
1 2013 1 9 641 900 1301 1242
2 2013 6 15 1432 1935 1137 1607
3 2013 1 10 1121 1635 1126 1239
4 2013 9 20 1139 1845 1014 1457
5 2013 7 22 845 1600 1005 1044
6 2013 4 10 1100 1900 960 1342
7 2013 3 17 2321 810 911 135
8 2013 6 27 959 1900 899 1236
9 2013 7 22 2257 759 898 121
10 2013 12 5 756 1700 896 1058
# ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
# arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
# origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
# minute <dbl>, time_hour <time>
Selecionar apenas as datas dos voos:
# A tibble: 336,776 x 3
year month day
<int> <int> <int>
1 2013 1 1
2 2013 1 1
3 2013 1 1
4 2013 1 1
5 2013 1 1
6 2013 1 1
7 2013 1 1
8 2013 1 1
9 2013 1 1
10 2013 1 1
# ... with 336,766 more rows
Ordenar os voos por maior recuperação de tempo por distancia:
# A tibble: 336,776 x 7
flight dep_delay arr_delay distance air_time gain gain_rate
<int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 3322 26 -21 94 26 -47 -108.46154
2 3544 21 -21 94 24 -42 -105.00000
3 4191 -3 -44 94 24 -41 -102.50000
4 4218 122 78 94 27 -44 -97.77778
5 3793 58 16 94 26 -42 -96.92308
6 3864 47 9 94 24 -38 -95.00000
7 4218 -2 -37 94 23 -35 -91.30435
8 4191 84 46 94 25 -38 -91.20000
9 3793 -6 -47 94 27 -41 -91.11111
10 3793 19 -20 94 26 -39 -90.00000
# ... with 336,766 more rows
# A tibble: 16 x 2
carrier mean.gain
<chr> <dbl>
1 9E -9.06
2 AA -8.20
3 AS -15.76
4 B6 -3.51
5 DL -7.58
6 EV -4.04
7 F9 1.72
8 FL 1.51
9 HA -11.82
10 MQ 0.33
11 OO -0.66
12 UA -8.46
13 US -1.62
14 VX -10.99
15 WN -8.01
16 YV -3.34
Contar quantos voos houve por cada dia do ano:
# A tibble: 365 x 2
dep.date count
<chr> <int>
1 2013-01-01 842
2 2013-01-02 943
3 2013-01-03 914
4 2013-01-04 915
5 2013-01-05 720
6 2013-01-06 832
7 2013-01-07 933
8 2013-01-08 899
9 2013-01-09 902
10 2013-01-10 932
# ... with 355 more rows
# A tibble: 4,037 x 3
tailnum delay count
<chr> <dbl> <int>
1 D942DN 31.500 4
2 N0EGMQ 9.983 352
3 N10156 12.717 145
4 N102UW 2.938 48
5 N103US -6.935 46
6 N104UW 1.804 46
7 N10575 20.691 269
8 N105UW -0.267 45
9 N107US -5.732 41
10 N108UW -1.250 60
# ... with 4,027 more rows
O histograma anterior mostra a existência de atrasos médios muito grandes (há voos com mais de 300 minutos de atraso!). Mas se usarmos outra visualização verificamos que isso ocorre quando o número de amostras por voo é pequeno:
Queremos estudar o atraso médio para cada destino em relação à sua distância:
flights %>%
group_by(dest) %>%
summarise(
count = n(),
dist = mean(distance, na.rm = TRUE),
delay = mean(arr_delay, na.rm = TRUE)) %>%
filter(count > 20) # apenas voos com suficiente amostras
# A tibble: 96 x 4
dest count dist delay
<chr> <int> <dbl> <dbl>
1 ABQ 254 1826.0000 4.381890
2 ACK 265 199.0000 4.852273
3 ALB 439 143.0000 14.397129
4 ATL 17215 757.1082 11.300113
5 AUS 2439 1514.2530 6.019909
6 AVL 275 583.5818 8.003831
7 BDL 443 116.0000 7.048544
8 BGR 375 378.0000 8.027933
9 BHM 297 865.9966 16.877323
10 BNA 6333 758.2135 11.812459
# ... with 86 more rows
df1 <- data.frame(id=1:3, m1=c(.1,.2,.3))
df2 <- data.frame(id=2:4, m2=letters[1:3])
df1
id m1
1 1 0.1
2 2 0.2
3 3 0.3
df2
id m2
1 2 a
2 3 b
3 4 c
left_join(df1,df2,by="id")
id m1 m2
1 1 0.1 <NA>
2 2 0.2 a
3 3 0.3 b
right_join(df1,df2,by="id")
id m1 m2
1 2 0.2 a
2 3 0.3 b
3 4 NA c
inner_join(df1,df2,by="id")
id m1 m2
1 2 0.2 a
2 3 0.3 b
full_join(df1,df2,by="id")
id m1 m2
1 1 0.1 <NA>
2 2 0.2 a
3 3 0.3 b
4 4 NA c