programacion - Visualice distancias entre textos
manual de programacion android pdf (7)
¿Estás haciendo todas las comparaciones por parejas? Depende de cómo se calcula la distancia (similitud), no estoy seguro si es posible hacer una gráfica de dispersión. así que cuando solo tiene 3 archivos de texto para tener en cuenta, su diagrama de dispersión es fácil de hacer (el triángulo con lados equivale a las distancias). pero cuando agrega el cuarto punto, es posible que no pueda colocarlo en una ubicación donde sus distancias a los 3 puntos existentes satisfagan todas las restricciones.
Pero si puedes hacer eso, entonces tienes una solución, solo agrega nuevos puntos una y otra vez ... Creo ... O, si no necesitas las distancias en la gráfica de dispersión para ser preciso, puedes simplemente hacer una web y etiquetar la distancia.
Estoy trabajando en un proyecto de investigación para la escuela. He escrito un software de minería de textos que analiza textos legales en una colección y arroja un puntaje que indica qué tan similares son. Ejecuté el programa para comparar cada texto con cualquier otro texto, y tengo datos como este (aunque con muchos más puntos):
codeofhammurabi.txt crete.txt 0.570737
codeofhammurabi.txt iraqi.txt 1.13475
codeofhammurabi.txt magnacarta.txt 0.945746
codeofhammurabi.txt us.txt 1.25546
crete.txt iraqi.txt 0.329545
crete.txt magnacarta.txt 0.589786
crete.txt us.txt 0.491903
iraqi.txt magnacarta.txt 0.834488
iraqi.txt us.txt 1.37718
magnacarta.txt us.txt 1.09582
Ahora tengo que trazarlos en un gráfico. Puedo invertir fácilmente los puntajes de modo que un pequeño valor ahora indique textos que sean similares y un valor grande indique textos que son diferentes: el valor puede ser la distancia entre puntos en un gráfico que representa los textos.
codeofhammurabi.txt crete.txt 1.75212
codeofhammurabi.txt iraqi.txt 0.8812
codeofhammurabi.txt magnacarta.txt 1.0573
codeofhammurabi.txt us.txt 0.7965
crete.txt iraqi.txt 3.0344
crete.txt magnacarta.txt 1.6955
crete.txt us.txt 2.0329
iraqi.txt magnacarta.txt 1.1983
iraqi.txt us.txt 0.7261
magnacarta.txt us.txt 0.9125
VERSIÓN CORTA: Los valores directamente superiores son distancias entre puntos en un diagrama de dispersión (1.75212 es la distancia entre el punto codeofhammurabi y el punto crete). Puedo imaginar un gran sistema de ecuaciones con círculos que representan las distancias entre puntos. ¿Cuál es la mejor manera de hacer este gráfico? Tengo MATLAB, R, Excel y acceso a casi cualquier software que pueda necesitar.
Si puedes incluso apuntarme en una dirección, estaré infinitamente agradecido.
Aquí hay una solución potencial para Matlab:
Puede organizar sus datos en una matriz de similitud formal S 5x5 donde el elemento S (i, j) representa su similitud (o desemejanza) entre el documento i y el documento j . Suponiendo que su medida de distancia es una métrica real, puede aplicar escalamiento multidimensional a esta matriz a través de mdscale (S, 2) .
Esta función intentará encontrar una representación dimensional de 5x2 de sus datos que preserve la similitud (o desemejanza) entre las clases que se encuentran en las dimensiones superiores. A continuación, puede visualizar estos datos como un diagrama de dispersión de 5 puntos.
También podría probar esto usando mdscale (S, 3) para proyectar en una matriz dimensional de 5x3 que luego puede visualizar con plot3 ().
Este fragmento de Matlab debería funcionar si quieres probar una vista de barra 3D:
% Load data from file ''dist.dat'', with values separated by spaces
fid = fopen(''dist.dat'');
data = textscan( ...
fid, ''%s%s%f'', ...
''Delimiter'', '' '', ...
''MultipleDelimsAsOne'', true ...
);
fclose(fid);
% Find all unique sources
text_bodies = unique(reshape([data{1:2}],[],1));
% Iterate trough the records and complete similarity matrix
N = numel(text_bodies);
similarity = NaN(N,N);
for k = 1:size(data{1},1)
n1 = find(strcmp(data{1}{k}, text_bodies));
n2 = find(strcmp(data{2}{k}, text_bodies));
similarity(n1, n2) = data{3}(k); % Symmetrical part ignored
end;
% Display #D bar chart
bar3(similarity);
Podría hacer un gráfico de red usando igraph. El diseño de Fruchterman-Reingold tiene un parámetro para proporcionar pesos de borde. Los pesos más grandes que 1 resultan en más "atracción" a lo largo de los bordes, los pesos inferiores a 1 hacen lo contrario. En su ejemplo, crete.txt tiene la distancia más baja y se encuentra en el medio y tiene bordes más pequeños a otros vértices. De hecho, está más cerca de iraqi.txt. Tenga en cuenta que debe invertir los datos de E (g) $ peso para obtener las distancias correctas.
data1 <- read.table(text="
codeofhammurabi.txt crete.txt 0.570737
codeofhammurabi.txt iraqi.txt 1.13475
codeofhammurabi.txt magnacarta.txt 0.945746
codeofhammurabi.txt us.txt 1.25546
crete.txt iraqi.txt 0.329545
crete.txt magnacarta.txt 0.589786
crete.txt us.txt 0.491903
iraqi.txt magnacarta.txt 0.834488
iraqi.txt us.txt 1.37718
magnacarta.txt us.txt 1.09582")
par(mar=c(3,7,3.5,5), las=1)
library(igraph)
g <- graph.data.frame(data1, directed = FALSE)
E(g)$weight <- 1/data1[,3] #inversed, high weights = more attraction along the edges
l <- layout.fruchterman.reingold(g, weights=E(g)$weight)
plot(g, layout=l)
Si quiere círculos que representen las distancias entre puntos, esto funcionaría en R (utilicé la primera tabla en su ejemplo):
data1 <- read.table(text="
codeofhammurabi.txt crete.txt 0.570737
codeofhammurabi.txt iraqi.txt 1.13475
codeofhammurabi.txt magnacarta.txt 0.945746
codeofhammurabi.txt us.txt 1.25546
crete.txt iraqi.txt 0.329545
crete.txt magnacarta.txt 0.589786
crete.txt us.txt 0.491903
iraqi.txt magnacarta.txt 0.834488
iraqi.txt us.txt 1.37718
magnacarta.txt us.txt 1.09582")
par(mar=c(3,7,3.5,5), las=1)
symbols(data1[,1],data1[,2], circles=data1[,3], inches=0.55, bg="lightblue", xaxt="n", yaxt="n", ylab="")
axis(1, at=data1[,1],labels=data1[,1])
axis(2, at=data1[,2],labels=data1[,2])
text(data1[,1], data1[,2], round(data1[,3],2), cex=0.9)
Sus datos son realmente distancias (de alguna forma) en el espacio multivariable abarcado por el corpus de palabras contenido en los documentos. Los datos de disimilitud como estos a menudo se ordenan para proporcionar el mejor mapeo k- d de las diferencias. El análisis de coordenadas principales y el escalamiento multidimensional no métrico son dos de estos métodos. Sugeriría que grafiques los resultados de aplicar uno u otro de estos métodos a tus datos. Proporciono ejemplos de ambos a continuación.
Primero, cargue los datos que suministró (sin etiquetas en esta etapa)
con <- textConnection("1.75212
0.8812
1.0573
0.7965
3.0344
1.6955
2.0329
1.1983
0.7261
0.9125
")
vec <- scan(con)
close(con)
Lo que efectivamente tienes es la siguiente matriz de distancia:
mat <- matrix(ncol = 5, nrow = 5)
mat[lower.tri(mat)] <- vec
colnames(mat) <- rownames(mat) <-
c("codeofhammurabi","crete","iraqi","magnacarta","us")
> mat
codeofhammurabi crete iraqi magnacarta us
codeofhammurabi NA NA NA NA NA
crete 1.75212 NA NA NA NA
iraqi 0.88120 3.0344 NA NA NA
magnacarta 1.05730 1.6955 1.1983 NA NA
us 0.79650 2.0329 0.7261 0.9125 NA
R, en general, necesita un objeto disímil de la clase "dist"
. Podríamos usar as.dist(mat)
ahora para obtener dicho objeto, o podríamos saltear la creación de mat
e ir directamente al objeto "dist"
como este:
class(vec) <- "dist"
attr(vec, "Labels") <- c("codeofhammurabi","crete","iraqi","magnacarta","us")
attr(vec, "Size") <- 5
attr(vec, "Diag") <- FALSE
attr(vec, "Upper") <- FALSE
> vec
codeofhammurabi crete iraqi magnacarta
crete 1.75212
iraqi 0.88120 3.03440
magnacarta 1.05730 1.69550 1.19830
us 0.79650 2.03290 0.72610 0.91250
Ahora que tenemos un objeto del tipo correcto, podemos ordenarlo. R tiene muchos paquetes y funciones para hacer esto (ver las vistas de tareas multivariantes o de entornos en CRAN), pero usaré el paquete vegano ya que estoy algo familiarizado con él ...
require("vegan")
Coordenadas principales
Primero, explico cómo hacer un análisis de coordenadas principales en sus datos usando vegan .
pco <- capscale(vec ~ 1, add = TRUE)
pco
> pco
Call: capscale(formula = vec ~ 1, add = TRUE)
Inertia Rank
Total 10.42
Unconstrained 10.42 3
Inertia is squared Unknown distance (euclidified)
Eigenvalues for unconstrained axes:
MDS1 MDS2 MDS3
7.648 1.672 1.098
Constant added to distances: 0.7667353
El primer eje PCO es con diferencia el más importante a la hora de explicar las diferencias entre textos, tal como lo muestran los valores propios. Ahora se puede producir un gráfico de ordenación trazando los vectores propios del PCO, utilizando el método de plot
plot(pco)
que produce
Escalado multidimensional no métrico
Una escala multidimensional no métrica (nMDS) no intenta encontrar una representación dimensional baja de las distancias originales en un espacio euclidiano. En su lugar, trata de encontrar un mapeo en k dimensiones que conserve mejor el orden jerárquico de las distancias entre observaciones. No existe una solución cerrada a este problema (a diferencia del PCO aplicado anteriormente) y se requiere un algoritmo iterativo para proporcionar una solución. Se recomienda iniciar al azar para asegurarte de que el algoritmo no ha convergido a una solución óptima localmente inferior a la óptima. La función metaMDS
de Vegan incorpora estas características y más. Si quiere nMDS antiguo simple, entonces vea isoMDS
en el paquete MASS .
set.seed(42)
sol <- metaMDS(vec)
> sol
Call:
metaMDS(comm = vec)
global Multidimensional Scaling using monoMDS
Data: vec
Distance: user supplied
Dimensions: 2
Stress: 0
Stress type 1, weak ties
No convergent solutions - best solution after 20 tries
Scaling: centring, PC rotation
Species: scores missing
Con este pequeño conjunto de datos, podemos representar esencialmente el orden jerárquico de las diferencias a la perfección (de ahí la advertencia, no se muestra). Se puede lograr un diagrama usando el método de plot
plot(sol, type = "text", display = "sites")
que produce
En ambos casos, la distancia en la trama entre muestras es la mejor aproximación bidimensional de su desemejanza. En el caso de la trama PCO, es una aproximación 2D de la diferencia real (se necesitan 3 dimensiones para representar todas las diferencias por completo), mientras que en la gráfica nMDS, la distancia entre muestras en la trama refleja la diferencia de rango no la diferencia real entre las observaciones. Pero esencialmente las distancias en la trama representan las diferencias calculadas. Los textos que están muy juntos son muy similares, los textos ubicados muy separados en la trama son los más diferentes entre sí.
Si la pregunta es ''¿cómo puedo hacer algo como lo hizo este tipo ?'' (del comentario de xiii1408 a la pregunta), entonces la respuesta es usar el algoritmo Force Atlas 2 de Gephi en las distancias euclidianas de las probabilidades posteriores del tema del documento .
"Este tipo" es Matt Jockers, que es un investigador innovador en humanidades digitales. Él ha documentado algunos de sus métodos en su blog y en otros lugares , etc. Jockers trabaja principalmente en R
y comparte parte de su código . Su flujo de trabajo básico parece ser:
- divide texto plano en trozos de 1000 palabras,
- eliminar las palabras vacías (no detener),
- hacer etiquetas de voz parcial y mantener solo los nombres,
- construir un modelo de tema (usando LDA),
- calcular las distancias euclidianas entre los documentos en función de las proporciones del tema, subconjuntos de las distancias para mantener solo los que están por debajo de un cierto umbral, y luego
- visualizar con un gráfico dirigido por la fuerza
Aquí hay un ejemplo reproducible a pequeña escala en R
(con una exportación a Gephi) que podría estar cerca de lo que hizo Jockers:
#### prepare workspace
# delete current objects and clear RAM
rm(list = ls(all.names = TRUE))
gc()
Obtener datos...
#### import text
# working from the topicmodels package vignette
# using collection of abstracts of the Journal of Statistical Software (JSS) (up to 2010-08-05).
install.packages("corpus.JSS.papers", repos = "http://datacube.wu.ac.at/", type = "source")
data("JSS_papers", package = "corpus.JSS.papers")
# For reproducibility of results we use only abstracts published up to 2010-08-05
JSS_papers <- JSS_papers[JSS_papers[,"date"] < "2010-08-05",]
Limpiar y remodelar ...
#### clean and reshape data
# Omit abstracts containing non-ASCII characters in the abstracts
JSS_papers <- JSS_papers[sapply(JSS_papers[, "description"], Encoding) == "unknown",]
# remove greek characters (from math notation, etc.)
library("tm")
library("XML")
remove_HTML_markup <- function(s) tryCatch({
doc <- htmlTreeParse(paste("<!DOCTYPE html>", s),
asText = TRUE, trim = FALSE)
xmlValue(xmlRoot(doc))
}, error = function(s) s)
# create corpus
corpus <- Corpus(VectorSource(sapply(JSS_papers[, "description"], remove_HTML_markup)))
# clean corpus by removing stopwords, numbers, punctuation, whitespaces, words <3 characters long..
skipWords <- function(x) removeWords(x, stopwords("english"))
funcs <- list(tolower, removePunctuation, removeNumbers, stripWhitespace, skipWords)
corpus_clean <- tm_map(corpus, wordLengths=c(3,Inf), FUN = tm_reduce, tmFuns = funcs)
Parte del etiquetado de voz y la subconfiguración de sustantivos ...
#### Part-of-speach tagging to extract nouns only
library("openNLP", "NLP")
# function for POS tagging
tagPOS <- function(x) {
s <- NLP::as.String(x)
## Need sentence and word token annotations.
a1 <- NLP::Annotation(1L, "sentence", 1L, nchar(s))
a2 <- NLP::annotate(s, openNLP::Maxent_Word_Token_Annotator(), a1)
a3 <- NLP::annotate(s, openNLP::Maxent_POS_Tag_Annotator(), a2)
## Determine the distribution of POS tags for word tokens.
a3w <- a3[a3$type == "word"]
POStags <- unlist(lapply(a3w$features, `[[`, "POS"))
## Extract token/POS pairs (all of them): easy - not needed
# POStagged <- paste(sprintf("%s/%s", s[a3w], POStags), collapse = " ")
return(unlist(POStags))
}
# a loop to do POS tagging on each document and do garbage cleaning after each document
# first prepare vector to hold results (for optimal loop speed)
corpus_clean_tagged <- vector(mode = "list", length = length(corpus_clean))
# then loop through each doc and do POS tagging
# warning: this may take some time!
for(i in 1:length(corpus_clean)){
corpus_clean_tagged[[i]] <- tagPOS(corpus_clean[[i]])
print(i) # nice to see what we''re up to
gc()
}
# subset nouns
wrds <- lapply(unlist(corpus_clean), function(i) unlist(strsplit(i, split = " ")))
NN <- lapply(corpus_clean_tagged, function(i) i == "NN")
Noun_strings <- lapply(1:length(wrds), function(i) unlist(wrds[i])[unlist(NN[i])])
Noun_strings <- lapply(Noun_strings, function(i) paste(i, collapse = " "))
# have a look to see what we''ve got
Noun_strings[[1]]
[8] "variogram model splus user quality variogram model pairs locations measurements variogram nonstationarity outliers variogram fit sets soil nitrogen concentration"
Modelado de temas con asignación latente de Dirichlet ...
#### topic modelling with LDA (Jockers uses the lda package and MALLET, maybe topicmodels also, I''m not sure. I''m most familiar with the topicmodels package, so here it is. Note that MALLET can be run from R: https://gist.github.com/benmarwick/4537873
# put the cleaned documents back into a corpus for topic modelling
corpus <- Corpus(VectorSource(Noun_strings))
# create document term matrix
JSS_dtm <- DocumentTermMatrix(corpus)
# generate topic model
library("topicmodels")
k = 30 # arbitrary number of topics (they are ways to optimise this)
JSS_TM <- LDA(JSS_dtm, k) # make topic model
# make data frame where rows are documents, columns are topics and cells
# are posterior probabilities of topics
JSS_topic_df <- setNames(as.data.frame(JSS_TM@gamma), paste0("topic_",1:k))
# add row names that link each document to a human-readble bit of data
# in this case we''ll just use a few words of the title of each paper
row.names(JSS_topic_df) <- lapply(1:length(JSS_papers[,1]), function(i) gsub("//s","_",substr(JSS_papers[,1][[i]], 1, 60)))
Calcule distancias euclidianas de un documento a otro usando probabilidades de temas como el ''ADN'' del documento
#### Euclidean distance matrix
library(cluster)
JSS_topic_df_dist <- as.matrix(daisy(JSS_topic_df, metric = "euclidean", stand = TRUE))
# Change row values to zero if less than row minimum plus row standard deviation
# This is how Jockers subsets the distance matrix to keep only
# closely related documents and avoid a dense spagetti diagram
# that''s difficult to interpret (hat-tip: http://.com/a/16047196/1036500)
JSS_topic_df_dist[ sweep(JSS_topic_df_dist, 1, (apply(JSS_topic_df_dist,1,min) + apply(JSS_topic_df_dist,1,sd) )) > 0 ] <- 0
Visualice usando un gráfico dirigido por la fuerza ...
#### network diagram using Fruchterman & Reingold algorithm (Jockers uses the ForceAtlas2 algorithm which is unique to Gephi)
library(igraph)
g <- as.undirected(graph.adjacency(JSS_topic_df_dist))
layout1 <- layout.fruchterman.reingold(g, niter=500)
plot(g, layout=layout1, edge.curved = TRUE, vertex.size = 1, vertex.color= "grey", edge.arrow.size = 0.1, vertex.label.dist=0.5, vertex.label = NA)
Y si desea utilizar el algoritmo Force Atlas 2 en Gephi, simplemente exporte el objeto gráfico R
a un archivo graphml
y luego ábralo en Gephi y configure el diseño en Force Atlas 2:
# this line will export from R and make the file ''JSS.graphml'' in your working directory ready to open with Gephi
write.graph(g, file="JSS.graphml", format="graphml")
Aquí está el diagrama de Gephi con el algoritmo Force Atlas 2: