14.3 Escalonamento Não-Métrico Multimensional (NMDS)
A NMDS é uma técnica de ordenação multivariada que permite visualizar graficamente distâncias entre objetos.
No R há várias funções que executam isso: isoMDS()
; cmdscale()
; e nmds()
e bestnmds()
do pacote labdsv
(Roberts 2019).
Vamos usar a função bestnmds()
nos exemplos abaixo.
Para entender, veja um exemplo para distâncias geográficas entre cidades na região norte do Brasil. Se queremos representar graficamente distâncias geográficas, estaremos de certa forma reproduzindo um mapa:
Vamos utilizar o conjunto de dados contendo coordenadas geográficas de municípios do Brasil para esta prática, utilizado na seção 3.4.2.
# visualizando distancias usando NMDS
# Vamos usar o arquivo com coordenadas dos municipios brasileiros
<- read.table(file = "municipiosbrasil.csv", header = T, as.is = T, sep = "\t", na.strings = c("NA", "", "NULL")) muni
Para simplificar, vamos filtrar apenas algumas cidades da região Norte:
<- c("Rio Branco", "Cruzeiro do Sul", "Tabatinga", "São Gabriel da Cachoeira", "Manaus", "Santarém", "Porto Velho", "Humaitá", "Belém", "Macapá", "Marabá", "Boa Vista")
cids
# filtrando os dados
<- muni$Municipio %in% cids
vl <- muni[vl, ]
muni
# calcula a distancia geografica entre essas cidades (em graus de latitude).
# Idealmente deveríamos converter latitude e longitude em décimos de graus para UTM para obter distancias em km ou m.
<- dist(muni[, c("Longitude", "Latitude")], method = "euclidean") mdist
# calculando um nmds em dois eixos (reduzindo a variação na matriz em dois eixos)
?bestnmds
Vamos utilizar a função bestnmds
do pacote labdsv
Vamos agora instalar o pacote e carregar o pacote labdsv
:
<- bestnmds(mdist, k = 2) onmds
## [1] 3.557932e-13 8.167216e-03 2.228642e+01 3.791436e+01 6.603351e-03
## [6] 6.029306e-03 2.109747e+01 9.041554e-03 4.415954e-03 4.861851e-01
## [11] 4.001459e-03 8.141675e-03 9.111469e-03 3.737914e+01 6.265940e-03
## [16] 4.124426e+01 9.818992e+00 2.702450e+01 3.316186e-03 3.712228e+01
##
## best result = 1
## with stress = 3.557932e-13
# veja a estrutura do resultado
str(onmds)
## List of 3
## $ points: num [1:21, 1:2] -18.2 -12.9 17.3 -14.7 -16.4 ...
## ..- attr(*, "dimnames")=List of 2
## .. ..$ : chr [1:21] "3" "14" "74" "135" ...
## .. ..$ : NULL
## $ stress: num 3.56e-13
## $ type : chr "NMDS"
## - attr(*, "class")= chr [1:2] "dsvord" "nmds"
## - attr(*, "call")= language bestnmds(dis = mdist, k = 2)
## - attr(*, "timestamp")= chr "Fri Oct 6 18:30:20 2023"
# o valor do stress indica o ajuste. Se o stress for 0, o ajuste é perfeito:
# a posição dos pontos é proporcional a distância entre eles
$stress onmds
## [1] 3.557932e-13
# quanto da variação foi explicada?
# essa pergunta a gente faz com PCA, não faz sentido fazer com NMDS.
# Mas, se você quer ter uma ideia de quanto um eixo capturou da variação,
# pode correlacionar a matriz de distancia original com uma matriz de distancia
# gerada pelos valores dos eixos nmds
# pega os valores dos eixos NMDS
<- onmds$points
ptsnmds # calcula a distancia
<- dist(ptsnmds)
adist # qual a correlacao entre essas matrizes de distancia?
cor(mdist, adist)
## [1] 1
# por isso o stress é baixo
# vamos comparar graficamente:
# divide o dispositivo em duas partes
par(mfrow = c(2, 1), mar = c(5, 5, 1, 1))
# adiciona limite no eixo X e y
<- range(ptsnmds[, 1]) + c(-1, 10)
xl <- range(ptsnmds[, 2]) + c(-2, 2)
yl # plota pontos
plot(ptsnmds, type = "p", pch = 21, bg = "red", xlab = "NMDS 1", ylab = "NMDS 2", xlim = xl, ylim = yl, cex = 0.5)
# adiciona o nome das cidades
text(ptsnmds, labels = muni$Municipio, cex = 0.8, pos = 4)
<- range(muni$Longitude) + c(-2, 5)
xl <- range(muni$Latitude) + c(-2, 2)
yl plot(muni$Longitude, muni$Latitude, type = "p", xlab = "Longitude", ylab = "Latitude", xlim = xl, ylim = yl, pch = 21, cex = 0.5, col = "blue")
text(muni$Longitude, muni$Latitude, labels = muni$Municipio, cex = 0.8, pos = 4)
14.3.1 Exemplo com dados morfológicos
Um exemplo de NMDS para mostra a similaridade entre indivíduos de Iris a partir de uma matriz de distância morfológica usando os dados de iris
do R.
data(iris) # #carrega o conjunto de dados de iris para a area de trabalho
head(iris)
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species |
---|---|---|---|---|
5.1 | 3.5 | 1.4 | 0.2 | setosa |
4.9 | 3.0 | 1.4 | 0.2 | setosa |
4.7 | 3.2 | 1.3 | 0.2 | setosa |
4.6 | 3.1 | 1.5 | 0.2 | setosa |
5.0 | 3.6 | 1.4 | 0.2 | setosa |
5.4 | 3.9 | 1.7 | 0.4 | setosa |
# calcula a distancia morfológica
?dist
<- dist(iris[, 1:4], method = "eucl") dmorfo
# calculando um nmds em dois eixos
<- bestnmds(dmorfo, k = 2) onmds
# ops objetos 102 e 143 devem ser identificos
c(102, 143), ] iris[
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species | |
---|---|---|---|---|---|
102 | 5.8 | 2.7 | 5.1 | 1.9 | virginica |
143 | 5.8 | 2.7 | 5.1 | 1.9 | virginica |
# entao eliminamos 1, porque senao nao funciona
<- iris[-102, ] iris2
# calculamos novamente a distancia
<- dist(iris2[, 1:4], method = "eucl")
dmorfo # calculando um nmds em dois eixos
<- bestnmds(dmorfo, k = 2) onmds
# veja a estrutura do resultado
str(onmds)
## List of 3
## $ points: num [1:149, 1:2] -2.67 -2.71 -2.87 -2.74 -2.72 ...
## ..- attr(*, "dimnames")=List of 2
## .. ..$ : chr [1:149] "1" "2" "3" "4" ...
## .. ..$ : NULL
## $ stress: num 2.58
## $ type : chr "NMDS"
## - attr(*, "class")= chr [1:2] "dsvord" "nmds"
## - attr(*, "call")= language bestnmds(dis = dmorfo, k = 2)
## - attr(*, "timestamp")= chr "Fri Feb 5 10:50:06 2021"
# o valor do stress indica o ajuste. Se stress for 0, o ajuste é perfeito
$stress onmds
## [1] 2.579978
# quanto da variação foi explicada?
# essa pergunta a gente faz com PCA, não faz sentido fazer com NMDS.
# Mas, se você quer ter uma ideia de quanto um eixo capturou da variação,
# pode correlacionar a matriz de distancia original
# com uma matriz de distancia gerada pelos valores dos eixos nmds
# pega os valores dos eixos NMDS
<- onmds$points
ptsnmds # cada linha nessa tabela corresponde
# à mesma linha na tabela iris2
head(ptsnmds)
-2.673344 | 0.2751282 |
-2.705573 | -0.1645018 |
-2.866471 | -0.0970515 |
-2.737119 | -0.2591412 |
-2.717433 | 0.2980718 |
-2.296804 | 0.6687755 |
# essas colunas são os dois eixos NMDS
# isso deve ser verdadeiro
nrow(ptsnmds) == nrow(iris2)
## [1] TRUE
# calcula a distancia entre os pontos pelos eixos NMDS
<- dist(ptsnmds)
adist # qual a correlacao entre essa matriz e a original?
cor(dmorfo, adist)
## [1] 0.9988093
# por isso o stress é baixo, a correlação é alta
Agora vamos fazer com outro índice de distância chamado gower, por exemplo, que é um bom índice quando se tem dados semiquantitativos na matriz (não é o caso aqui).
Vamos utilizar a função vegdist()
do pacote vegan
(Oksanen et al. 2020).
data(iris)
<- iris[-102, ] iris2
<- vegdist(iris2[, 1:4], method = "gower")
dmorfo2 <- bestnmds(dmorfo2, k = 2) onmds2
# pega os valores dos eixos NMDS
<- onmds2$points
ptsnmds2 # cada linha nessa tabela corresponde
# à mesma linha na tabela iris2
head(ptsnmds2)
-0.2907098 | 0.0456498 |
-0.2712303 | -0.0244397 |
-0.3013570 | -0.0090738 |
-0.2933819 | -0.0245441 |
-0.3066071 | 0.0494793 |
-0.2595641 | 0.1165398 |
# essas colunas são os dois eixos NMDS
# isso deve ser verdadeiro
nrow(ptsnmds2) == nrow(iris2)
## [1] TRUE
# calcula a distancia entre os pontos pelos eixos NMDS
<- dist(ptsnmds2)
adist2 # qual a correlacao entre essa matriz e a original?
cor(dmorfo2, adist2)
## [1] 0.9950709
# por isso o stress é baixo, a correlação é alta
# vamos visualizar os dois resultados, onmds e onmds2, graficamente
# divide o dispositivo em dois
par(mfrow = c(2, 1), mar = c(5, 5, 1, 1))
<- c("red", "green", "blue")[as.numeric(iris2$Species)]
cores plot(ptsnmds, pch = 21, bg = cores, cex = 0.8, xlab = "NMDS 1", ylab = "NMDS 2", main = "Objeto: ptsnmds")
plot(ptsnmds2, pch = 21, bg = cores, cex = 0.8, xlab = "NMDS 1", ylab = "NMDS 2", main = "Objeto: ptsnmds2")