(Re)Formatação de Bases de Dados
0 Mudar a Forma dos Dados
Uma tabela pode estar limpa e ainda não ter a forma de que precisamos. Por exemplo, duas fases dum estudo podem estar em colunas separadas, quando nos daria mais jeito ter uma coluna com a fase e outra com a resposta. Vamos fazer essa mudança e guardar o resultado. Depois, reorganizamos uma folha com medidas de duas espécies de briozoários.
Em ambos os casos, a pergunta importante é: o que representa uma linha antes e depois da transformação? Criar mais linhas não cria mais participantes nem mais organismos. Mudamos a organização das mesmas medições.
Use a consola para experimentar: veja algumas linhas, conte os registos, faça a transformação e volte a olhar para o resultado. Guarde no script os passos que quer repetir. As consultas de exploração não precisam de acompanhar todas as execuções. Se mudar os dados ou a transformação, volte a inspeccionar.
Preparar os Ficheiros
Precisamos das operações dos tutoriais de data.frames, de importação e de limpeza. Descarregue demo_ds.csv e bryozoan_raw.csv para a pasta data do seu projecto.
Como nos tutoriais anteriores, os caminhos supõem que trabalhamos a partir de programming, ao lado de data. Em ../data/demo_ds.csv, .. significa subir uma pasta; depois entramos em data e abrimos o ficheiro. Se a sua organização for outra, ajuste os caminhos de leitura e de escrita.
Vamos usar tidyr para reorganizar as tabelas e dplyr para transformar colunas e seleccionar linhas. Se necessário, instale os pacotes uma vez na consola, fora do script, como explicado na referência de pacotes. No início do script, carregamo-los uma só vez:
1 Psicologia: Duas Fases por Pessoa
O ficheiro demo_ds.csv tem uma linha por participante e duas colunas com respostas, phase1 e phase2. Queremos juntar essas respostas numa única coluna, mantendo a indicação da fase a que pertencem. Lemos o ficheiro recebido:
ds <- read.csv("../data/demo_ds.csv")Na consola, escrever ds imprime a tabela; head(ds) mostra apenas as primeiras linhas. Para tabelas grandes, usamos head(ds) como pré-visualização. nrow() conta as linhas:
head(ds)| pp_number | consents | age | gender | cond | mood | rts | phase1 | phase2 | lab |
|---|---|---|---|---|---|---|---|---|---|
| 1 | Yes | 19 | female | high | 31 | 2106 | 18 | 80 | B |
| 2 | Yes | 23 | female | high | 25 | 2249 | 27 | 83 | A |
| 3 | Yes | 41 | female | high | 28 | 3248 | 23 | 95 | B |
| 4 | Yes | 26 | female | high | 29 | 1931 | 30 | 81 | A |
| 5 | Yes | 26 | female | high | 39 | 2410 | 28 | 88 | B |
| 6 | Yes | 42 | female | high | 27 | 3451 | 18 | 89 | A |
nrow(ds)[1] 120
Há 120 linhas. pp_number identifica o participante, enquanto phase1 e phase2 guardam as suas respostas nas duas fases. Chamamos a esta organização formato largo (wide): neste caso, as fases ocupam colunas diferentes. Não vamos aplicar critérios de inclusão neste exemplo; esses passos são tratados na limpeza.
Largo para Longo
Vamos passar a ter uma linha por participante e fase. A função pivot_longer() junta as colunas escolhidas:
ds <- pivot_longer(
ds, cols = c(phase1, phase2),
names_to = "phase", values_to = "response"
)ds é a tabela a transformar. cols indica as duas colunas que vamos alongar. names_to = "phase" dá nome à coluna que recebe os nomes originais, "phase1" e "phase2". values_to = "response" dá nome à coluna que recebe as respostas. As restantes colunas são conservadas e repetidas nas duas linhas de cada pessoa.
Na consola, voltamos a imprimir e a contar, agora depois do pivô:
head(ds)| pp_number | consents | age | gender | cond | mood | rts | lab | phase | response |
|---|---|---|---|---|---|---|---|---|---|
| 1 | Yes | 19 | female | high | 31 | 2106 | B | phase1 | 18 |
| 1 | Yes | 19 | female | high | 31 | 2106 | B | phase2 | 80 |
| 2 | Yes | 23 | female | high | 25 | 2249 | A | phase1 | 27 |
| 2 | Yes | 23 | female | high | 25 | 2249 | A | phase2 | 83 |
| 3 | Yes | 41 | female | high | 28 | 3248 | B | phase1 | 23 |
| 3 | Yes | 41 | female | high | 28 | 3248 | B | phase2 | 95 |
nrow(ds)[1] 240
Temos 240 linhas, duas por participante. Este é o formato longo (long) para as fases. pivot_longer() devolve um tibble, uma forma de tabela cuja impressão na consola mostra normalmente só algumas linhas e indica as dimensões no início. Use head(ds[, c("pp_number", "phase", "response")]) para ver juntas as colunas que definem esta mudança. Numa página Quarto, a apresentação de tabelas pode mostrar mais linhas do que a consola; head() limita explicitamente a saída.
Antes, pp_number bastava para identificar uma linha. Agora precisamos de pp_number e phase: são duas respostas da mesma pessoa, não 240 pessoas independentes. Colunas como age ou rts repetem-se porque vieram da linha original. O pivô não recolheu uma nova idade nem um novo tempo de resposta em cada fase.
Esta organização é útil para gráficos e análises com medidas repetidas. Quando quiser analisar as diferenças entre fases, consulte o tutorial de medidas repetidas e a explicação da dependência entre medições.
Guardar o Resultado
Vamos escrever a tabela em ../data/demo_long.csv, num ficheiro separado do recebido. Execute a escrita depois de inspeccionar o resultado. Os blocos de escrita e os scripts completos não são executados automaticamente nesta página.
write.csv(ds, "../data/demo_long.csv", row.names = FALSE)write.csv() recebe a tabela e o caminho de destino. row.names = FALSE evita acrescentar uma coluna com os números das linhas do R. O identificador pp_number continua nos dados, tal como phase e response.
A escrita não imprime habitualmente uma mensagem de sucesso. Procure demo_long.csv na pasta data. Também podemos voltar a lê-lo na consola:
Se voltar a guardar com o mesmo caminho, write.csv() substitui o ficheiro de destino; não acrescenta linhas no fim. Aqui, demo_ds.csv é apenas lido e demo_long.csv é a saída. Confirme o nome para não gravar por cima do original.
Os exemplos usam data, que já existe. Se preferir uma pasta results, crie-a uma vez pelo gestor de ficheiros ou com dir.create("../results") na consola e ajuste o destino. write.csv() cria o ficheiro, mas não cria a pasta.
O Script Completo
Depois da exploração, guardamos estes passos em wrangle_demo.R. O script é autónomo: com os pacotes já instalados, podemos executá-lo de cima para baixo a partir de programming.
Ficaram os passos que queremos repetir, sem as consultas da consola. Para recomeçar, lemos novamente o original. Não tentamos alongar uma segunda vez a tabela que já está longa.
2 Biologia: Duas Espécies Lado a Lado
O ficheiro bryozoan_raw.csv contém medidas de estádio (STAGE), corrida (RUN), massa (LOG10MASS) e taxa metabólica (LOG10MR). As medidas estão em dois blocos de colunas, um para Bugula e outro para Watersipora. Queremos uma única tabela com uma coluna species e quatro colunas para as medidas.
Os blocos têm comprimentos diferentes. A posição na folha não emparelha organismos das duas espécies: a primeira linha de Bugula e a primeira de Watersipora apenas ocupam a mesma linha do ficheiro.
Lemos os dados preservando os espaços nos nomes das colunas:
ds <- read.csv(
"../data/bryozoan_raw.csv", check.names = FALSE
)Na consola, olhamos para a tabela, os nomes e a contagem:
head(ds)| BUGULA STAGE | BUGULA RUN | BUGULA LOG10MASS | BUGULA LOG10MR | WATERSIPORA STAGE | WATERSIPORA RUN | WATERSIPORA LOG10MASS | WATERSIPORA LOG10MR |
|---|---|---|---|---|---|---|---|
| LARVAE | 1 | 1.124187 | -0.0718051 | LARVAE | 1 | 1.255952 | -0.0615924 |
| LARVAE | 1 | 1.214295 | -0.3606221 | LARVAE | 1 | 1.289612 | -0.0926305 |
| LARVAE | 1 | 1.224337 | -0.0769383 | LARVAE | 1 | 1.403698 | -0.1224853 |
| LARVAE | 1 | 1.282665 | -0.1383318 | LARVAE | 1 | 1.313851 | -0.2449149 |
| LARVAE | 1 | 1.230768 | -0.0141452 | LARVAE | 1 | 1.289418 | 0.0110419 |
| LARVAE | 1 | 1.138109 | -0.1255545 | LARVAE | 1 | 1.200366 | -0.2453349 |
names(ds)[1] "BUGULA STAGE" "BUGULA RUN" "BUGULA LOG10MASS"
[4] "BUGULA LOG10MR" "WATERSIPORA STAGE" "WATERSIPORA RUN"
[7] "WATERSIPORA LOG10MASS" "WATERSIPORA LOG10MR"
nrow(ds)[1] 535
Há 535 linhas e oito colunas. check.names = FALSE impede que read.csv() troque os espaços por pontos. Assim, mantemos nomes como BUGULA STAGE e WATERSIPORA LOG10MASS. Cada nome tem duas partes: a espécie e a medida, separadas por um espaço.
Juntar os Blocos
Vamos usar essas duas partes para organizar a nova tabela:
ds <- pivot_longer(
ds, cols = everything(),
names_to = c("species", ".value"), names_sep = " "
)everything() selecciona todas as colunas, porque todas pertencem aos blocos de medidas. names_sep = " " separa cada nome no espaço literal. A primeira parte vai para species, com valores BUGULA ou WATERSIPORA.
A segunda parte merece atenção: .value é uma indicação especial para usar essa parte como nome duma coluna de valores. Por exemplo, BUGULA STAGE e WATERSIPORA STAGE alimentam a mesma coluna STAGE, mas em linhas de espécies diferentes. O mesmo acontece com RUN, LOG10MASS e LOG10MR. Não precisamos de values_to, porque vamos manter quatro colunas de valores, em vez de juntar medidas diferentes numa só.
Na consola, vemos o que o pivô produziu:
head(ds)| species | STAGE | RUN | LOG10MASS | LOG10MR |
|---|---|---|---|---|
| BUGULA | LARVAE | 1 | 1.124187 | -0.0718051 |
| WATERSIPORA | LARVAE | 1 | 1.255952 | -0.0615924 |
| BUGULA | LARVAE | 1 | 1.214295 | -0.3606221 |
| WATERSIPORA | LARVAE | 1 | 1.289612 | -0.0926305 |
| BUGULA | LARVAE | 1 | 1.224337 | -0.0769383 |
| WATERSIPORA | LARVAE | 1 | 1.403698 | -0.1224853 |
nrow(ds)[1] 1070
tail(ds)| species | STAGE | RUN | LOG10MASS | LOG10MR |
|---|---|---|---|---|
| BUGULA | LATE | 12 | -0.8188841 | -0.3085966 |
| WATERSIPORA | NA | NA | NA | |
| BUGULA | LATE | 12 | -0.8499565 | -0.2873348 |
| WATERSIPORA | NA | NA | NA | |
| BUGULA | LATE | 12 | -0.6472585 | -0.3400670 |
| WATERSIPORA | NA | NA | NA |
Temos 1070 linhas: duas por posição da folha original. No fim aparecem registos de Bugula intercalados com linhas vazias de Watersipora. Estas últimas têm STAGE igual a "" e as três medidas numéricas iguais a NA. São espaço de preenchimento, necessário para colocar lado a lado blocos com comprimentos diferentes.
Como vimos na limpeza, neste ficheiro os registos de Watersipora terminam na linha 288 do bloco original. Podemos retirar o preenchimento depois de separar as espécies:
# In this file, an empty STAGE marks structural padding, not an organism.
ds <- filter(ds, STAGE != "")Na consola, imprimimos novamente e contamos o que ficou:
head(ds)| species | STAGE | RUN | LOG10MASS | LOG10MR |
|---|---|---|---|---|
| BUGULA | LARVAE | 1 | 1.124187 | -0.0718051 |
| WATERSIPORA | LARVAE | 1 | 1.255952 | -0.0615924 |
| BUGULA | LARVAE | 1 | 1.214295 | -0.3606221 |
| WATERSIPORA | LARVAE | 1 | 1.289612 | -0.0926305 |
| BUGULA | LARVAE | 1 | 1.224337 | -0.0769383 |
| WATERSIPORA | LARVAE | 1 | 1.403698 | -0.1224853 |
nrow(ds)[1] 823
tail(ds)| species | STAGE | RUN | LOG10MASS | LOG10MR |
|---|---|---|---|---|
| BUGULA | LATE | 12 | -0.9563585 | -0.3968164 |
| BUGULA | LATE | 12 | -0.8670110 | -0.3029143 |
| BUGULA | LATE | 12 | -0.8027612 | -0.4486721 |
| BUGULA | LATE | 12 | -0.8188841 | -0.3085966 |
| BUGULA | LATE | 12 | -0.8499565 | -0.2873348 |
| BUGULA | LATE | 12 | -0.6472585 | -0.3400670 |
Ficaram 823 registos, 535 de Bugula e 288 de Watersipora. Retirámos 247 linhas de preenchimento, não 247 organismos. O filtro usa a estrutura que inspeccionámos neste ficheiro; noutro conjunto de dados, um estádio em falta pode coexistir com medições válidas e não justifica, por si só, excluir o registo.
Cada linha contém agora um registo duma espécie, com estádio, corrida e duas medidas. LOG10MASS e LOG10MR continuam em log10: o pivô não alterou a escala. species identifica a espécie, não um organismo. Nem a posição original, nem a combinação de espécie, estádio e corrida fornecem aqui um identificador individual. Para ligar medições do mesmo organismo ou decidir quais são independentes, precisamos dos registos da recolha; não basta numerar as linhas.
O Script Completo
Guardamos o percurso em wrangle_bryozoans.R. A saída fica em ../data/bryozoans_long.csv, separada de bryozoan_raw.csv. Como no primeiro exemplo, uma nova execução substitui apenas essa saída, desde que conservemos estes caminhos distintos.
library(dplyr)
library(tidyr)
ds <- read.csv(
"../data/bryozoan_raw.csv", check.names = FALSE
)
ds <- pivot_longer(
ds, cols = everything(),
names_to = c("species", ".value"), names_sep = " "
)
# In this file, an empty STAGE marks structural padding, not an organism.
ds <- filter(ds, STAGE != "")
write.csv(ds, "../data/bryozoans_long.csv", row.names = FALSE)O CSV terá 823 linhas de dados e cinco colunas. Podemos voltar a executar estes scripts para ler os dados recebidos, reorganizar a tabela e guardar o resultado. Quando a forma já servir a sua pergunta, pode continuar com as descritivas e os gráficos. As secções seguintes ficam como referência para outras transformações.
3 Transformar Variáveis
Além de mudar a forma, podemos calcular novas colunas. Uma conversão de unidades é um bom exemplo: rts contém tempos de resposta em milissegundos. Vamos voltar a ler a tabela de psicologia para trabalhar com uma linha por pessoa, sem depender do objecto de biologia:
mutate() acrescenta ou transforma colunas. Para conservar os valores originais, damos um nome novo à coluna calculada:
# Keep milliseconds and add seconds.
ds <- mutate(ds, rts_s = rts / 1000)Na consola, vemos agora os segundos:
head(ds$rts_s)[1] 2.106 2.249 3.248 1.931 2.410 3.451
Dividimos por 1000 porque há 1000 milissegundos num segundo. Não mudou o número de pessoas nem a duração medida, apenas a unidade. Se quisermos conservar esta transformação no script de psicologia, podemos colocá-la entre a leitura e o pivô. A coluna rts_s será repetida nas duas fases, tal como rts; não passa a ser uma medição específica de cada fase.
Podemos também transformar uma variável sem estar apenas a converter unidades. A escolha deve fazer sentido para a medida e para a análise; não é uma etapa obrigatória da preparação.
scale() permite criar z-scores: subtrai a média e divide pelo desvio-padrão. Cada unidade da nova coluna corresponde a um desvio-padrão. Com os dados de psicologia que acabámos de ler:
ds <- mutate(ds, rts_z = as.numeric(scale(rts)))
head(ds$rts_z)[1] -1.58639138 -1.40783885 -0.16046836 -1.80489972 -1.20681117 0.09300131
Um valor próximo de 0 está próximo da média; 1 está um desvio-padrão acima e -1 um desvio-padrão abaixo. as.numeric() guarda o resultado como uma coluna numérica simples.
Outra possibilidade é o logaritmo. log() calcula o logaritmo natural, log10() o de base 10. Precisamos de valores positivos: log(0) produz -Inf e números negativos não têm logaritmo real. Neste ficheiro, os tempos são positivos:
Ao contrário da conversão para segundos, o logaritmo muda a relação entre distâncias na escala. Não se aplica automaticamente para «corrigir» uma variável. No exemplo de biologia, as duas medidas já estão em log10; não devemos voltar a aplicar o logaritmo por rotina. Para justificar a escala e a interpretação duma transformação, consulte a Teoria da Medida.
4 Longo para Largo
pivot_wider() faz o percurso inverso. Vamos retomar as duas fases, criando a forma longa a partir do ficheiro original. Desta vez guardamos o resultado em phases para o distinguir da versão larga:
phases <- read.csv("../data/demo_ds.csv")
phases <- pivot_longer(
phases, cols = c(phase1, phase2),
names_to = "phase", values_to = "response"
)
head(phases)| pp_number | consents | age | gender | cond | mood | rts | lab | phase | response |
|---|---|---|---|---|---|---|---|---|---|
| 1 | Yes | 19 | female | high | 31 | 2106 | B | phase1 | 18 |
| 1 | Yes | 19 | female | high | 31 | 2106 | B | phase2 | 80 |
| 2 | Yes | 23 | female | high | 25 | 2249 | A | phase1 | 27 |
| 2 | Yes | 23 | female | high | 25 | 2249 | A | phase2 | 83 |
| 3 | Yes | 41 | female | high | 28 | 3248 | B | phase1 | 23 |
| 3 | Yes | 41 | female | high | 28 | 3248 | B | phase2 | 95 |
nrow(phases)[1] 240
Há 240 linhas. Para voltar a uma linha por participante, escolhemos explicitamente o identificador, a origem dos nomes e a dos valores:
wide <- pivot_wider(
phases, id_cols = pp_number,
names_from = phase, values_from = response
)
head(wide)| pp_number | phase1 | phase2 |
|---|---|---|
| 1 | 18 | 80 |
| 2 | 27 | 83 |
| 3 | 23 | 95 |
| 4 | 30 | 81 |
| 5 | 28 | 88 |
| 6 | 18 | 89 |
nrow(wide)[1] 120
Temos 120 linhas e três colunas: pp_number, phase1 e phase2. id_cols indica o que identifica cada linha do resultado; names_from indica de onde vêm os nomes das novas colunas e values_from indica onde estão os valores. Escolhemos conservar apenas o identificador e as respostas neste exemplo. Outras colunas podem entrar em id_cols se forem constantes dentro da pessoa e quisermos conservá-las.
Se houver várias respostas para o mesmo participante e fase, pivot_wider() pode produzir colunas de listas e um aviso. Primeiro perceba a razão: uma duplicação acidental e vários ensaios legítimos pedem decisões diferentes. No segundo caso, pode faltar uma coluna que identifique o ensaio. Não use values_fn = mean só para fazer desaparecer o aviso; fazer uma média é uma decisão sobre as medições.
5 Referência Avançada
Mergulhar Mais Fundo
Na primeira leitura, continue nos exercícios. Os exemplos abaixo ficam para quando precisar destas técnicas.
As técnicas seguintes ajudam quando os nomes das colunas contêm várias dimensões ou quando precisamos de reorganizar a tabela em etapas. Os dados pequenos desta secção são inventados para mostrar a estrutura. Pode voltar aqui quando encontrar um ficheiro com estes problemas.
names_sep e names_pattern
Quando o separador é regular, names_sep basta, como o espaço nos nomes dos briozoários. names_to pode receber vários nomes, um para cada parte que queremos extrair:
measures <- data.frame(
pp = 1:3,
rt_easy_short = c(500, 520, 480),
rt_easy_long = c(700, 680, 720),
rt_hard_short = c(650, 640, 670),
rt_hard_long = c(900, 880, 920)
)
by_separator <- pivot_longer(
measures, -pp,
names_to = c("measure", "difficulty", "length"),
names_sep = "_", values_to = "value"
)
by_separator| pp | measure | difficulty | length | value |
|---|---|---|---|---|
| 1 | rt | easy | short | 500 |
| 1 | rt | easy | long | 700 |
| 1 | rt | hard | short | 650 |
| 1 | rt | hard | long | 900 |
| 2 | rt | easy | short | 520 |
| 2 | rt | easy | long | 680 |
| 2 | rt | hard | short | 640 |
| 2 | rt | hard | long | 880 |
| 3 | rt | easy | short | 480 |
| 3 | rt | easy | long | 720 |
| 3 | rt | hard | short | 670 |
| 3 | rt | hard | long | 920 |
-pp selecciona todas as colunas excepto pp. Cada nome é dividido nos _: por exemplo, rt_easy_short dá rt, easy e short. Uma linha passa a representar uma pessoa numa combinação de dificuldade e comprimento.
Com names_pattern usamos uma expressão regular (regex), um padrão que descreve o texto. Os parêntesis delimitam partes a extrair, os grupos de captura. O exemplo anterior também pode escrever-se:
by_pattern <- pivot_longer(
measures, -pp,
names_pattern = "^(rt)_(easy|hard)_(short|long)$",
names_to = c("measure", "difficulty", "length"),
values_to = "value"
)
by_pattern| pp | measure | difficulty | length | value |
|---|---|---|---|---|
| 1 | rt | easy | short | 500 |
| 1 | rt | easy | long | 700 |
| 1 | rt | hard | short | 650 |
| 1 | rt | hard | long | 900 |
| 2 | rt | easy | short | 520 |
| 2 | rt | easy | long | 680 |
| 2 | rt | hard | short | 640 |
| 2 | rt | hard | long | 880 |
| 3 | rt | easy | short | 480 |
| 3 | rt | easy | long | 720 |
| 3 | rt | hard | short | 670 |
| 3 | rt | hard | long | 920 |
^ marca o início e $ o fim do nome. (rt) captura rt; (easy|hard) captura uma das duas dificuldades, pois | significa «ou». O terceiro grupo captura o comprimento. Os três grupos alimentam, pela mesma ordem, as três colunas de names_to.
Se um nome não corresponder ao padrão, pode aparecer NA na informação extraída. Compare os nomes originais antes de decidir o que fazer. Para experimentar o padrão na consola, grepl() devolve um valor lógico por nome:
[1] TRUE TRUE FALSE FALSE
Obtemos TRUE, TRUE, FALSE, FALSE. As marcas de início e fim impedem que apenas parte dum nome seja aceite. A referência de regex explica outros padrões. Para os explorar visualmente, pode usar regex101, escolhendo PCRE2. Há diferenças entre motores de regex: confirme o padrão final na função de R em que o vai usar.
Separar Medidas e Condições
Nos briozoários, .value usou a segunda parte do nome para criar as colunas de medidas. Também pode ocupar a primeira posição. Aqui, rt e acc são duas medidas, e easy e hard duas dificuldades:
conditions <- data.frame(
pp = 1:3,
rt_easy = c(500, 520, 480), acc_easy = c(.90, .85, .95),
rt_hard = c(800, 760, 820), acc_hard = c(.75, .80, .70)
)
conditions_long <- pivot_longer(
conditions, -pp,
names_to = c(".value", "difficulty"), names_sep = "_"
)
conditions_long| pp | difficulty | rt | acc |
|---|---|---|---|
| 1 | easy | 500 | 0.90 |
| 1 | hard | 800 | 0.75 |
| 2 | easy | 520 | 0.85 |
| 2 | hard | 760 | 0.80 |
| 3 | easy | 480 | 0.95 |
| 3 | hard | 820 | 0.70 |
Temos seis linhas, uma por pessoa e dificuldade, com rt e acc em colunas separadas. Sem .value, juntaríamos tempos e proporções numa coluna de valores, perdendo esta separação entre medidas. O mesmo princípio funciona com regex:
conditions_pattern <- pivot_longer(
conditions, -pp,
names_pattern = "^(rt|acc)_(easy|hard)$",
names_to = c(".value", "difficulty")
)
conditions_pattern| pp | difficulty | rt | acc |
|---|---|---|---|
| 1 | easy | 500 | 0.90 |
| 1 | hard | 800 | 0.75 |
| 2 | easy | 520 | 0.85 |
| 2 | hard | 760 | 0.80 |
| 3 | easy | 480 | 0.95 |
| 3 | hard | 820 | 0.70 |
Alargar Várias Medidas
values_from pode receber vários nomes. Se names_from também tiver várias colunas, o pivô combina os seus níveis nos nomes finais:
long_with_group <- expand_grid(
pp = 1:3, difficulty = c("easy", "hard"), block = c("A", "B")
) |>
mutate(
rt = 450 + 40 * (difficulty == "hard") + 10 * (block == "B") + pp,
acc = .95 - .10 * (difficulty == "hard") - .02 * (block == "B")
)
wide_multiple <- pivot_wider(
long_with_group, id_cols = pp,
names_from = c(difficulty, block), values_from = c(rt, acc),
names_sep = "_"
)
wide_multiple| pp | rt_easy_A | rt_easy_B | rt_hard_A | rt_hard_B | acc_easy_A | acc_easy_B | acc_hard_A | acc_hard_B |
|---|---|---|---|---|---|---|---|---|
| 1 | 451 | 461 | 491 | 501 | 0.95 | 0.93 | 0.85 | 0.83 |
| 2 | 452 | 462 | 492 | 502 | 0.95 | 0.93 | 0.85 | 0.83 |
| 3 | 453 | 463 | 493 | 503 | 0.95 | 0.93 | 0.85 | 0.83 |
expand_grid() cria todas as combinações dos valores fornecidos; mutate() acrescenta duas medidas inventadas às 12 linhas. O resultado largo tem uma linha por pessoa. Nomes como rt_easy_A indicam a medida, a dificuldade e o bloco. Aqui, names_sep controla o separador dos nomes criados, em vez de separar nomes existentes.
Alongar e Voltar a Alargar
Nem sempre compensa transformar tudo numa única chamada. Esta tabela tem dois momentos e duas medidas por pessoa:
| pp | age | pre_anxiety | pre_depression | post_anxiety | post_depression |
|---|---|---|---|---|---|
| 1 | 25 | 15 | 10 | 10 | 5 |
| 2 | 30 | 20 | 15 | 15 | 10 |
| 3 | 35 | 18 | 12 | 14 | 8 |
nrow(messy)[1] 3
Primeiro separamos momento e medida, ficando com uma pontuação por linha:
clean_long <- pivot_longer(
messy, -c(pp, age),
names_pattern = "^(pre|post)_(anxiety|depression)$",
names_to = c("time", "measure"), values_to = "score"
)
clean_long| pp | age | time | measure | score |
|---|---|---|---|---|
| 1 | 25 | pre | anxiety | 15 |
| 1 | 25 | pre | depression | 10 |
| 1 | 25 | post | anxiety | 10 |
| 1 | 25 | post | depression | 5 |
| 2 | 30 | pre | anxiety | 20 |
| 2 | 30 | pre | depression | 15 |
| 2 | 30 | post | anxiety | 15 |
| 2 | 30 | post | depression | 10 |
| 3 | 35 | pre | anxiety | 18 |
| 3 | 35 | pre | depression | 12 |
| 3 | 35 | post | anxiety | 14 |
| 3 | 35 | post | depression | 8 |
nrow(clean_long)[1] 12
Passámos de 3 para 12 linhas: cada pessoa tem dois momentos e duas medidas. Agora podemos escolher que dimensão queremos nas colunas. Para comparar os momentos lado a lado:
time_comparison <- pivot_wider(
clean_long, id_cols = c(pp, age, measure),
names_from = time, values_from = score
)
time_comparison| pp | age | measure | pre | post |
|---|---|---|---|---|
| 1 | 25 | anxiety | 15 | 10 |
| 1 | 25 | depression | 10 | 5 |
| 2 | 30 | anxiety | 20 | 15 |
| 2 | 30 | depression | 15 | 10 |
| 3 | 35 | anxiety | 18 | 14 |
| 3 | 35 | depression | 12 | 8 |
nrow(time_comparison)[1] 6
Há seis linhas, uma por pessoa e medida, com pre e post em colunas. Se quisermos antes uma linha por pessoa e momento, alargamos a medida:
by_measure <- pivot_wider(
clean_long, id_cols = c(pp, age, time),
names_from = measure, values_from = score
)
by_measure| pp | age | time | anxiety | depression |
|---|---|---|---|---|
| 1 | 25 | pre | 15 | 10 |
| 1 | 25 | post | 10 | 5 |
| 2 | 30 | pre | 20 | 15 |
| 2 | 30 | post | 15 | 10 |
| 3 | 35 | pre | 18 | 12 |
| 3 | 35 | post | 14 | 8 |
nrow(by_measure)[1] 6
Também há seis linhas, mas o significado é diferente: as colunas de valores são agora anxiety e depression. A contagem ajuda a perceber a mudança; precisamos de olhar para as colunas para saber o que cada linha representa. Os dois percursos são úteis para perguntas diferentes.
Para outros argumentos, consulte a documentação oficial de pivot_longer() e de pivot_wider().
6 Exercícios
Os exercícios 5 e 6 são opcionais e acompanham a referência avançada.
- Reconstitua
wrangle_demo.Rsem copiar o bloco completo. Na consola, veja as primeiras linhas e conte os registos antes e depois do pivô. Explique por que temos 240 linhas mas apenas 120 participantes.
Solução 1 — Participantes e Fases
Cada participante contribui com duas respostas, uma por fase. Por isso há 240 linhas e apenas 120 participantes. pp_number identifica a pessoa, enquanto a combinação pp_number e phase identifica cada linha longa. O pivô mudou a organização dos dados, sem criar novos participantes.
- Acrescente
rts_sao script de psicologia, antes do pivô, e guarde num destino novo. Explique as unidades e por que razão o tempo se repete nas duas linhas da mesma pessoa.
Solução 2 — Tempos em Segundos
Dividir rts por 1000 converte milissegundos em segundos. O pivô repete esta informação nas duas linhas da mesma pessoa porque rts pertence à pessoa e não a uma fase específica. Depois da transformação, convém confirmar unidades e valores nas primeiras linhas.
- Execute
wrangle_bryozoans.Ra partir do ficheiro recebido. Abra o CSV de saída. Explique para que serve.valuee por que razão retirámos 247 linhas. O que aconteceria se omitíssemoscheck.names = FALSEmantendo o mesmonames_sep?
Solução 3 — Os Dados de Briozoários
.value mantém STAGE, RUN, LOG10MASS e LOG10MR em colunas diferentes. Ficamos com 823 registos depois de retirar o preenchimento vazio do bloco mais curto. Sem check.names = FALSE, os espaços nos nomes passariam a pontos e names_sep = " " já não os dividiria nas partes pretendidas.
- Volte a ler
demo_long.csve usepivot_wider()para obter uma linha por pessoa, apenas com o identificador e as duas respostas. Veja as primeiras linhas e conte os registos antes e depois. Guarde o resultado com outro nome.
Solução 4 — Voltar ao Formato Largo
Depois de ler a saída num objecto phases, podemos escrever pivot_wider(phases, id_cols = pp_number, names_from = phase, values_from = response). Passamos de 240 para 120 linhas, com três colunas. A escrita deve usar um destino diferente e row.names = FALSE.
- Na referência avançada, adapte o exemplo de
.valuea nomes comoscore_pre,score_post,time_preetime_post. Explique o que representa cada linha do resultado.
Solução 5 — Separar Medida e Momento
Com nomes como score_pre e time_post, podemos usar names_to = c(".value", "occasion") e names_sep = "_". O resultado tem uma linha por pessoa e momento, com score e time em colunas separadas. A combinação do identificador da pessoa e do momento identifica cada linha.
- No exemplo de pivôs em etapas, as duas tabelas finais têm seis linhas. Explique por que razão isso não significa que tenham a mesma organização. Indique as colunas necessárias para identificar uma linha em cada uma.
Solução 6 — A Mesma Contagem, Outra Organização
As duas tabelas finais têm seis linhas, mas time_comparison tem uma linha por pp e measure, enquanto by_measure tem uma linha por pp e time. age descreve a pessoa e não identifica as suas várias linhas. O número de linhas, por si só, não revela a unidade de observação.