elasticsearch, analizador spanish con filtro fonético

Pasos a seguir para crear un indice con un analizdor en español con reconocimiento de fonético.

Especialmente util si queremos buscar para un texto como este:

  • “Habia una vez un barquito chiquitito que navegaba ….”

palabras como

  • avia, nabegava

es decir, palabras mal escritas pero que se pronuncian igual.

Pasos previo necesarios

Instalar plugin

En cada nodo debemos instalar el plugin y reiniciar.

sudo bin/plugin install analysis-phonetic

Crear el indice y el mapping

curl -XDELETE http://localhost:9201/indice_fonetico

curl -XPUT http://localhost:9201/indice_fonetico

curl -XPOST http://localhost:9201/indice_fonetico/_close

curl -XPUT http://localhost:9201/indice_fonetico/_settings?pretty -d '{
  "analysis" : {
    "analyzer":{
      "spanish_no_accent": {
          "tokenizer": "standard",
          "filter":  [ "lowercase", "asciifolding", "spanish_stop", "filtro_fonetico", "spanish_stemmer" ]
          }
    },
    "filter" : {
            "spanish_stemmer" : {
                "type" : "stemmer",
                "name" : "spanish"
            },
            "spanish_stop": {
                             "type":        "stop",
                             "stopwords": [ "_spanish_" ]
      },
      "filtro_fonetico": {
          "type": "phonetic",
          "encoder": "beidermorse",
          "replace": false,
          "languageset": ["spanish"]
      }
          }
  }
}'

curl -XPOST http://localhost:9201/indice_fonetico/_open

curl -XPUT http://localhost:9201/indice_fonetico/_mapping/documento?pretty -d '{
"properties":{
  "documento":{
     "type":"string",
     "analyzer":"spanish_no_accent"
  }
 }
}'

Indexar datos y preparar una query

Indexamos un documento

PUT indice_fonetico/documento/1
{
  "documento": "Habia una vez un barquito chiquitito que navegaba ...."
}

Realizamos una búsqueda

  • avia

Query y respuesta

GET indice_fonetico/documento/_search
{
  "query": {
    "match": {
      "documento": {
        "query": "avia"
      }
    }
  }
}
{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 1.7499999,
    "hits": [
      {
        "_index": "indice_fonetico",
        "_type": "documento",
        "_id": "4",
        "_score": 1.7499999,
        "_source": {
          "documento": "Habia una vez un barquito chiquitito que navegaba ...."
        }
      }
    ]
  }
}

Términos generados por este analizador

En cualquier momento podemos ver los términos que genera este analizador, para entender mejor las búsquedas que podemos realizar con este analizador y el nuevo filtro fonético.

Invocación del analizador

POST prueba_fonetica/_analyze
{
  "analyzer": "spanish_no_accent",
  "text":     "barquito"
}

Términos generados por el analizador

{
  "tokens": [
    {
      "token": "barkit",
      "start_offset": 0,
      "end_offset": 8,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "borkit",
      "start_offset": 0,
      "end_offset": 8,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "varkit",
      "start_offset": 0,
      "end_offset": 8,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "vorkit",
      "start_offset": 0,
      "end_offset": 8,
      "type": "<ALPHANUM>",
      "position": 0
    }
  ]
}

Para entender mejor el comportamiento de este filtro fonético podemos crear un analizador donde solamente tengamos como filtro el filtro fonético.

curl -XPUT http://localhost:9201/indice_fonetico/_settings?pretty -d '{
  "analysis" : {
    "analyzer":{
      "spanish_no_accent": {
          "tokenizer": "standard",
          "filter":  [ "lowercase", "filtro_fonetico" ]
          }
    },
    "filter" : {
      "filtro_fonetico": {
          "type": "phonetic",
          "encoder": "beidermorse",
          "replace": false,
          "languageset": ["spanish"]
      }
    }
  }
}'
Anuncios

Copias de seguridad en elasticsearch, el API de snapshot y restore

Elasticsearch

El API de snapshot y restore de elasticsearch nos permite hacer copias de seguridad de los datos y el estado de

  • todo el cluster o
  • algunos indices

sobre un repositorio compartido, que puede ser

  • Amazon S3
  • Sistema de ficheros compartido
  • HDFS
  • Azure
  • etc

En cualquiera de los casos, independientemente del repositorio elegido, éste tiene que ser accesible desde cualquier nodo del cluster.

Elasticsearch soporta multitud de repositorios compartidos, todos ellos a través de la instalación de un plugin.

Nosotros vamos a ver como crear un snapshot y restore en un sistema de ficheros compartido.

El proceso de snapshot es incremental. Eso significa que el primer snapshot se realiza completo, mientras que el resto de snapshot solo tiene en cuenta la diferencia entre el último snapshot y los nuevos datos del cluster añadidos o modificados.

Registrar el path del sistema de ficheros

Antes de registrar el repositorio compartido, el cual hemos elegido en nuestro caso el sistema de ficheros, debemos registrar su ubicación. Recordemos que esta ubicación debe ser visible y accesible por todos los nodos del cluster.

Esta configuración debe ser añadida igualmente en todos los nodos del cluster.

Para ello editamos el fichero de configuración

$ES_HOME/config/elasticsearch.yml

y añadimos la configuración

path.repo: ["/mnt/fs-shared/backups/"]

Registrar el repositorio compartido

Una vez tenemos configurado la ubicación en todos los nodos del cluster, procedemos a registrar el repositorio compartido

# Petición
curl -XPUT 'http://localhost:9200/_snapshot/backups' -d '{
    "type": "fs",
        "settings": {
            "location": "/mnt/fs-shared/backups/"
    }
}'

# Respuesta
{
   "acknowledged": true
}

Donde podemos definir las siguientes características

  • location, puede ser un path relativo, en cuyo caso es relativo al path especificado en la configuración path.repo
  • compress, true o false. true por defecto. Indica si comprimir los ficheros de metadatos, es decir, los mappings, settings etc. Los ficheros de datos sin embargo no se comprimen
  • chunk_size, para dividir los ficheros grandes en otros más pequeños

Obtener información del repositorio compartido

# Petición
curl -XGET http://localhost:9200/_snapshot/backups

# Respuesta
{
   "backups": {
      "type": "fs",
      "settings": {
         "location": "/mnt/fs-shared/backups/"
      }
   }
}

Obtener información de todos los repositorios compartidos

curl -XGET http://localhost:9200/_snapshot

curl -XGET http://localhost:9200/_snapshot/_all

Verificación del registro de un repositorio

Cuando registramos un repositorio compartido, automáticamente se verifica su configuración en todos los nodos de cluster. Este comportamiento lo podemos modificar mediante un parámetro durante el registro del repositorio, evitando que este chequeo se realice. En esos casos, podemos lanzar posteriormente el proceso de verificación mediante la llamada al API:

# Petición
curl -XGET http://localhost:9200/_snapshot/backups/_verify

# Respuesta
{
   "nodes": {
      "NhD89B4NR1eI1kgl56TwQQ": {
         "name": "Architect"
      }
   }
}

Y como respuesta obtendremos la lista todos los nodos que han podido verificar el repositorio.

Creación de un snapshot de todo el cluster

Por defecto el snapshot se realiza del cluster completo. Exactamente de todos los índices abiertos.

# Petición
curl -XPUT http://localhost:9200/_snapshot/backups/backup_1?wait_for_completion=false

# Respuesta
{
   "accepted": true
}

Donde,

  • wait_for_completion, indica que no esperemos a que el snapshot termine. Aun así si el cluster es muy grande, esta operación puede tarde segundos o minutos, ya que antes de relizar el snapshot tiene que cargar en memoria la información del mismo.

Obtener información de un snapshot

# Petición
curl -XGET http://localhost:9200//_snapshot/backups/backup_1

# Respuesta
{
   "snapshots": [
      {
         "snapshot": "backup_1",
         "version_id": 1070199,
         "version": "1.7.1",
         "indices": [
            ".marvel-2015.09.24",
            "cars",
            ".marvel-2015.09.29",
            ".kibana_2",
            "pruebas",
            "usuarios",
            ".marvel-kibana",
            ".marvel-2015.10.16",
            ".kibana"
         ],
         "state": "SUCCESS",
         "start_time": "2015-11-11T16:59:43.224Z",
         "start_time_in_millis": 1447261183224,
         "end_time": "2015-11-11T17:13:31.158Z",
         "end_time_in_millis": 1447262011158,
         "duration_in_millis": 827934,
         "failures": [],
         "shards": {
            "total": 44,
            "failed": 0,
            "successful": 44
         }
      }
   ]
}

Como vemos el snapshot ha terminado con correctamente state=SUCCESS, y tenemos todos los índices del cluster. Además al estar utilizando kibana y marvel tenemos los índices que estos plugins crea automáticamente.

Creación de un snapshot pero solo de determinados índices

# Petición
curl -XPUT 'http://localhost:9200/_snapshot/backups/usuarios_y_cars_1' -d '{
  "indices": "usuarios,cars"
}'

# Respuesta
{
   "accepted": true
}

En este caso podemos indicarle las siguiente propiedades
– ignore_unavailable, si un índice no esta disponible el snapshot falle o no
– include_global_state, para no incluir o ignorar la información del estado del cluster

Igualmente podemos obtener la información del snapshot utilizando el mismo API Rest, pero en este caso, veremos solamente los indices seleccionados en el snapshot

# Petición
curl -XGET http://localhost:9200/_snapshot/backups/usuarios_y_cars_1

# Respuesta
{
   "snapshots": [
      {
         "snapshot": "usuarios_y_cars_1",
         "version_id": 1070199,
         "version": "1.7.1",
         "indices": [
            "usuarios",
            "cars"
         ],
         "state": "SUCCESS",
         "start_time": "2015-11-11T17:24:04.625Z",
         "start_time_in_millis": 1447262644625,
         "end_time": "2015-11-11T17:24:04.781Z",
         "end_time_in_millis": 1447262644781,
         "duration_in_millis": 156,
         "failures": [],
         "shards": {
            "total": 10,
            "failed": 0,
            "successful": 10
         }
      }
   ]
}

Información de todos los snapshot

curl -XGET http://localhost:9200/_snapshot/backups/_all

Borrado de un snapshot o parada de un snapshot

Esta operación se puede lanzar tanto para borrar un snapshot, como para parar uno que se encuenta en ejecución.

# Petición
curl -XDELETE http://localhost:9200/_snapshot/backups/backup_1

# Respuesta
{
   "acknowledged": true
}

Restaurar o recuperar un snapshot

Es la operación de restore. Debemos antes cerrar el índice o índices para poder realizar el restore. Además debemos asegurarnos que el número de shards no ha cambiado.

curl -XPOST http://localhost:9200/cars/_close
curl -XPOST http://localhost:9200/usuarios/_close
curl -XPOST http://localhost:9200/_snapshot/backups/usuarios_y_cars_1/_restore

# Respuesta
{
   "accepted": true
}

Una vez se ha restaurado el snapshot, los índices son abiertos automáticamente.

Por defecto se restauran todos los índices junto con el estado del cluster. Pero también se puede seleccionar del snapshot que índices queremos restaurar.

Por ejemplo, podemos restaurar solo el índice usuarios.

curl -XPOST http://localhost:9200/usuarios/_close
curl -XPOST 'http://localhost:9200/_snapshot/backups/usuarios_y_cars_1/_restore' -d '{
    "indices": "usuarios"
}'

# Respuesta
{
   "accepted": true
}

Restore nos permite otras funcionalidades, como la de poder hacer el restore en un cluster diferente, renombrar los índices etc

Determinar el estado de un snapshot

Tanto durante la ejecución de un snapshot como cuando ya ha termiando, podemos saber su estado.

La operación como siempre se realiza con el API Rest, y en la respuesta tenemos toda la información que necesitamos. Entre ellas nos encontraremos con la propiedad

  • state

cuyos valores no dicen el estado actual del snapshot, que pueden ser STARTED, SUCCESS, etc

# Lanzamos el snapshot
curl -XPUT http://localhost:9200/_snapshot/backups/all_cluster/

# Pedimos su estaso
curl -XGET http://localhost:9200/_snapshot/backups/all_cluster/_status

# Respuesta
{
   "snapshots": [
      {
         "snapshot": "all_cluster",
         "repository": "backups",
         "state": "STARTED",
         "shards_stats": {
            "initializing": 18,
            "started": 2,
            "finalizing": 0,
            "done": 25,
            "failed": 0,
            "total": 45
         },
         "stats": {
            ...
         },
         "indices": {
            ...
      }
   ]
}

Recordemos que un snapshot en ejecución o en proceso puede ser parado con el API Rest de borrado de snapshot.

Si te ha gustado este artículo o lo has encontrado interesante no olvides compartirlo en tus redes sociales. Esto nos ayuda a seguir mejorando.

El API Java de scan y scroll de elasticsearch

Elasticsearch

En el anterior artículo hemos visto las principales características del API Rest de scan y scroll de elasticsearch así como su utilización.

En este artículo vamos a ver cómo se utiliza el cliente Java para realizar lo mismo, sin entrar en detalles, ya que la descripción y característica son las mismas que el API Rest.

Fragmentos de código de utilización del API Java de Scan/scroll

public void iniciarScanScroll(String query) throws Exception {
    String scrollId = elasticSearchManagerImpl.prepararScanScroll(query, "nombre_campo");
    SearchResponse resp = elasticSearchManagerImpl.buscarScanScroll(scrollId);
    while (resp != null) {
        for (SearchHit hit : resp.getHits().getHits()) {
            /* Procesar los documentos */
        }
        resp = elasticSearchManagerImpl.buscarScanScroll(resp.getScrollId());
    }           
}


public String prepararScanScroll(String query, String field) {
    SearchResponse resp = client.prepareSearch(NOMBRE_INDICE)
        .setTypes(TIPO_DOCUMENTO)
        .setSearchType(SearchType.SCAN)
        .setScroll(new TimeValue(1000))         
        .setSize(500)
        .setQuery(QueryBuilders.queryStringQuery(query).field(field))
        .execute()
        .actionGet();
    return resp.getScrollId();
}

public SearchResponse buscarScanScroll(String scrollId) {
    SearchResponse resp = client.prepareSearchScroll(scrollId).setScroll(new TimeValue(1000)).execute().actionGet();        
    if (resp.getHits().getHits().length == 0) {
        return null;
    }
    return resp;
}

Donde,

  • client, es el cliente Java de elasticsearch
  • iniciarScanScroll, bucle principal donde iniciar y procesar el scan/scroll
  • prepararScanScroll, preparación del scan/scroll
  • buscarScanScroll, recuperación de cada una de las páginas

El API Rest de scan y scroll de elasticsearch

Elasticsearch

El tipo de búsqueda scan junto con el API de scroll nos permite :

  • Recuperar gran cantidad de documentos de forma eficiente
  • Realizar paginación o scroll sobre los resultados

Además,

  • este API toma un snapshot en el momento iniciar la primera búsqueda, de forma que aunque se modifique el índice, no afectará a los resultados de la búsqueda (ni a la primera página, ni a las siguientes)
  • en cada página o scroll se devuelve un nuevo scroll_id, que debe de ser pasado en las siguientes llamadas de este API
  • en cada página o scroll le indicamos un intervalo de tiempo, que será el tiempo máximo que tardaremos en procesar cada página
  • inicialmente indicaremos un tamaño de página, el cual como sabemos, será el resultado de multiplicar (ese tamaño) * (número de shards del íncide)

La forma de proceder es la siguiente

  • Se hace la busqueda de tipo scan
  • Se recupera el scroll_id
  • Se pide al API de scroll el primer bloque de documentos pasándole el scroll_id y nos guardamos el nuevo scroll_id generado
  • Se procesan los documentos
  • Se pide al API de scroll el siguiente bloque de documentos pasándole el nuevo scroll_id.
  • etc

Su similitud en una base de datos relacional sería un cursor.

Iniciamos el scan scroll

Petición

curl -XPOST 'http://localhost:9200/usuarios/_search?search_type=scan&scroll=5m&size=250' -d '{
  "query": {
    "query_string": {
      "default_field": "nombre",
      "query": "garcia"
    }
  }
}'

Respuesta

{
   "_scroll_id": "c2Nhbjs1OzkyOmEyalU1TDRIUTFxeDNwODhVUzlXX3c7OTA6YTJqVTVMNEhRMXF4M3A4OFVTOVdfdzs5MTphMmpVNUw0SFExcXgzcDg4VVM5V193Ozk0OmEyalU1TDRIUTFxeDNwODhVUzlXX3c7OTM6YTJqVTVMNEhRMXF4M3A4OFVTOVdfdzsxO3RvdGFsX2hpdHM6NjQ3Mjs=",
   "took": 30,
   "timed_out": false,
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "hits": {
      "total": 1567,
      "max_score": 0,
      "hits": []
   }
}

Como vemos en la respuesta no se devuelve ningún documento. Solo se informa del total de documentos que cumpleten la query y el scroll_id que debemos utilizar para pedir el primer batch o página de documentos.

Si tenemos un índice con 5 shards, el número total de documentos devueltos en cada página es de 5 * 250 = 1250 documentos

Primera página y resto de páginas

Petición

GET _search/scroll?scroll=1m&scroll_id=c2Nhbjs1OzE2NTphMmpVNUw0SFExcXgzcDg4VVM5V193OzE2MzphMmpVNUw0SFExcXgzcDg4VVM5V193OzE2NzphMmpVNUw0SFExcXgzcDg4VVM5V193OzE2NDphMmpVNUw0SFExcXgzcDg4VVM5V193OzE2NjphMmpVNUw0SFExcXgzcDg4VVM5V193OzE7dG90YWxfaGl0czo2NDcyOw==

Respuesta

{
   "_scroll_id": "c2NhbjswOzE7dG90YWxfaGl0czo2NDcyOw==",
   "took": 11,
   "timed_out": false,
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "hits": {
      "total": 1567,
      "max_score": 0,
      "hits": [
         {
            ...
         }
      ]
   }
}

Última página

Petición

GET _search/scroll?scroll=1m&scroll_id=c2NhbjswOzE7dG90YWxfaGl0czo2NDcyOw==

Respuesta

{
   "_scroll_id": "c2NhbjswOzE7dG90YWxfaGl0czo2NDcyOw==",
   "took": 0,
   "timed_out": false,
   "_shards": {
      "total": 0,
      "successful": 0,
      "failed": 0
   },
   "hits": {
      "total": 1567,
      "max_score": 0,
      "hits": []
   }
}

elasticsearch Cookbook, ver los términos generados por un analizador utilizando el API Java

Elasticsearch

Ya hemos visto en otro artículo cómo obtener los términos de un analizador utilizando el API Rest. Ahora vamos a ver lo mismo pero utilizando el API Java.

Como sabemos podemos obtener los términos generados por un analizador de dos formas distintas

  • invocando al API indicándole el nombre del analizador
  • invocando al API indicándole un nombre del campo para que utilice el analizador asociado a ese campo

En este artículo vamos a ver las dos opciones.

Invocando al API indicándole el nombre del analizador

public List<String> analyzer(String texto, String analyzer) {
    AnalyzeResponse aresp = client.admin().indices().analyze(new AnalyzeRequest(INDEX_NAME, texto).analyzer(analyzer)).actionGet();
    List<String> terms = new ArrayList<String>();
    if (aresp != null && aresp.getTokens() != null) {
        aresp.getTokens().forEach(token -> {
            terms.add(token.getTerm());
        });
    }
    return terms;
}

Invocando al API indicándole un nombre del campo

public List<String> analyzerByField(String texto, String field) {
    AnalyzeResponse aresp = client.admin().indices().analyze(new AnalyzeRequest(INDEX_NAME, texto).field(field)).actionGet();
    List<String> terms = new ArrayList<String>();
    if (aresp != null && aresp.getTokens() != null) {
        aresp.getTokens().forEach(token -> {
            terms.add(token.getTerm());
        });
    }
    return terms;
}

En ambos casos

  • client, es un objeto de la clase org.elasticsearch.client.Client obtenido utilizando el API Java de elasticsearch. Tanto con la clase NodeBuilder como TransportClient podemos obtener el cliente de acceso a elasticsearch.

    No vamos a ver como obtener este objeto client. Esperamos hacerlo en otro artículo que publicaremos más adelante.

elasticsearch Cookbook, obtener todos los valores diferentes de un campo

Elasticsearch

Otro ejemplo de utilización de agregaciones en elasticsearch. La similitud con una base de datos relacional sería la query

SELECT 
    DISTINCT(column_name)
FROM 
    table_name;

Y en elasticsearch podemos conseguir lo mismo utilizando la agregación terms. Esta agregación devuelve por defecto los 10 primeros términos más repetidos, ordenados por el número de veces que aparecen.

Si el campo donde vamos a aplicar la agregación es un campo de tipo string not_analyzed, obtendremos como resultado todos los valores distintos posibles para ese campo. Lo mismo que conseguiríamos con la sentencia SELECT DISTINCT(column_name) FROM table_name; en una base de datos relacional.

Si el campo es analizado, obtendremos todos los términos diferentes que existen en ese campo.

Este tipo de agregación podemos parametrizarlo.

  • Podemos cambiar el tipo de ordenación de los datos devueltos
  • Podemos cambiar el número de terminos devueltos en el resultado
  • Podemos cambiar el comportamiento del operador cuando tenemos varios shards, para minimizar el problema de que no siempre se devuelven los más repetidos. En ocasiones al tener multiples shards el operador no siempre va a devolver los más repetidos
  • Podemos cambiar la ordenación utilizando una segunda agregación de tipo estadísica y ordenar por uno de sus valores
  • etc

Vamos a verlo con un ejemplo.

Creación del índice y alta de nuevos documentos

POST usuarios
POST usuarios/_mapping/perfil
{
"properties": {
  "nombre": {
    "type": "string"
  },
  "name": {
    "type": "string",
    "index": "not_analyzed"
  }
}
}
POST usuarios/perfil
{
  "nombre":"michael jackson",
  "name":"michael jackson"
}
POST usuarios/perfil
{
  "nombre":"michael jordan",
  "name":"michael jordan"
}
POST usuarios/perfil
{
  "nombre":"michael johnson",
  "name":"michael johnson"
}

API Rest en un campo de tipo string analyzed

GET usuarios/perfil/_search?search_type=count
{
  "aggs": {
    "nombres_diferentes": {
      "terms": {
        "field": "nombre"
      }
    }
  }
}

# Respuesta
{
   "took": 3,
   "timed_out": false,
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "hits": {
      "total": 3,
      "max_score": 0,
      "hits": []
   },
   "aggregations": {
      "nombres_diferentes": {
         "doc_count_error_upper_bound": 0,
         "sum_other_doc_count": 0,
         "buckets": [
            {
               "key": "michael",
               "doc_count": 3
            },
            {
               "key": "jackson",
               "doc_count": 1
            },
            {
               "key": "johnson",
               "doc_count": 1
            },
            {
               "key": "jordan",
               "doc_count": 1
            }
         ]
      }
   }
}

API Rest en un campo de tipo string not_analyzed

GET usuarios/perfil/_search?search_type=count
{
  "aggs": {
    "nombres_diferentes": {
      "terms": {
        "field": "name"
      }
    }
  }
}

# Respuesta
{
   "took": 2,
   "timed_out": false,
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "hits": {
      "total": 3,
      "max_score": 0,
      "hits": []
   },
   "aggregations": {
      "nombres_diferentes": {
         "doc_count_error_upper_bound": 0,
         "sum_other_doc_count": 0,
         "buckets": [
            {
               "key": "michael jackson",
               "doc_count": 1
            },
            {
               "key": "michael johnson",
               "doc_count": 1
            },
            {
               "key": "michael jordan",
               "doc_count": 1
            }
         ]
      }
   }
}

Ejemplo de API Java

public void searchDistinct(String field, Integer size) {
public List<String> searchDistinct(String field, Integer size) {
    SearchResponse resp = client.prepareSearch()
        .setSearchType(SearchType.COUNT)
        .addAggregation(AggregationBuilders.terms("nombres_diferentes").field(field).size(size))
        .execute()
        .actionGet();
    return ((MultiBucketsAggregation)resp.getAggregations().get("nombres_diferentes")).getBuckets()
        .stream()
        .map((bucket) -> {
            return bucket.getKey();
        }
    ).collect(Collectors.toList());
}

NOTA. Todos los comandos de este artículo del tipo API Rest (comandos POST, GET, etc), son comandos que podemos lanzar directamente utilizando la interface Marvel de elasticsearch. Si no utilizamos Marvel, podemos sustituirlo por el comando de unix curl, tal y como lo hemos visto en otros artículos de la serie elasticsearch Cookbook de este blog.

elasticsearch Cookbook, obtener la media, desviación estándar y la varianza de los valores de un campo

Elasticsearch

Tenemos una forma directa y fácil de conseguirlo. Utilizaremos agregaciones, concretamente la agregación extended_stats. Solo tenemos que indicar el nombre de la agregaciób y el campo sobre el que necesitamos obtener estos valores.

En el ejemplo, hemos dado de alta 10 documentos, con los valores del campo valor a 1, 2, 3, 4, 5, 6, 7, 8, 9 y 10. Los valores que nos proporciona esta agregación son los siguientes:

  • count: 10, es el número total de documentos o valores analizados.
  • min: 1, el valor mínimo encontrado
  • max: 10, el valor máximo encontrado
  • avg: 5.5,, la media
  • sum: 55, la suma total de todos los valores
  • sum_of_squares: 385, la suma total de todos los valores al cuadrado
  • variance: 8.25, la varianza
  • std_deviation: 2.8722813232690143, la desviación estandar
  • std_deviation_bounds: son límites por encima y debajo de los valores devueltos para poder representarlos correctamente, en, por ejemplo, una gráfica

Relación entre varianza y desviación estándar

La desviación estándar sirve para identificar de una muestra de datos, cuales son los valores normales y los que sobresalen por encima o por debajo. Es decir, dada la media de la muestra, los valores comprendidos entre (la media + desviación estándar) y (la media – desviación estándar) son los valores normales, y los que se encuentren fuera de ese rango son los valores menos normales de la muestra. Así que nos informa de como se separan los valores respecto a toda la muestra.

La desviación estándar es la raíz cuadrada de la varianza. Y la varianza es la media de las diferencias con la media elevadas al cuadrado.

Por ejemplo, para los valores de la muestra 3, 7 y 8 vamos a calcular cada uno de estos valores

Media
media = (3+7+8)/3 = 6

Varianza
varianza = ((3 – 6)^2 + (7 – 6)^2 + (8 – 6)^2)/3 = (-3^2 + 1^2 + 2^2)/3 = (9 + 1 + 4)/3 = 4.66

Desviación estádar
desviación estándar= Raiz Cuadrada(4.66)= 2.15

Utilización de la agregación extended_stats

Ejemplo API Rest

POST pruebas/numeros/_search?search_type=count
{
  "aggs": {
    "estadisticas": {
      "extended_stats": {
        "field": "valor"
      }
    }
  }
}

Respuesta

{
   "took": 1,
   "timed_out": false,
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "hits": {
      "total": 10,
      "max_score": 0,
      "hits": []
   },
   "aggregations": {
      "estadisticas": {
         "count": 10,
         "min": 1,
         "max": 10,
         "avg": 5.5,
         "sum": 55,
         "sum_of_squares": 385,
         "variance": 8.25,
         "std_deviation": 2.8722813232690143,
         "std_deviation_bounds": {
            "upper": 11.244562646538029,
            "lower": -0.24456264653802862
         }
      }
   }
}