web-dev-qa-db-fra.com

Création de DataFrame à partir de résultats ElasticSearch

J'essaye de construire un DataFrame dans des pandas, en utilisant les résultats d'une requête très basique à ElasticSearch. Je reçois les données dont j'ai besoin, mais il suffit de découper les résultats pour créer le bloc de données approprié. Je ne tiens vraiment qu'à obtenir l’horodatage et le chemin de chaque résultat. J'ai essayé quelques modèles es.search différents.

Code:

from datetime import datetime
from elasticsearch import Elasticsearch
from pandas import DataFrame, Series
import pandas as pd
import matplotlib.pyplot as plt
es = Elasticsearch(Host="192.168.121.252")
res = es.search(index="_all", doc_type='logs', body={"query": {"match_all": {}}}, size=2, fields=('path','@timestamp'))

Cela donne 4 morceaux de données. [u'hits ', u'_shards', tu as pris ', u'timed_out']. Mes résultats sont à l'intérieur des hits.

res['hits']['hits']
Out[47]: 
[{u'_id': u'a1XHMhdHQB2uV7oq6dUldg',
  u'_index': u'logstash-2014.08.07',
  u'_score': 1.0,
  u'_type': u'logs',
  u'fields': {u'@timestamp': u'2014-08-07T12:36:00.086Z',
   u'path': u'app2.log'}},
 {u'_id': u'TcBvro_1QMqF4ORC-XlAPQ',
  u'_index': u'logstash-2014.08.07',
  u'_score': 1.0,
  u'_type': u'logs',
  u'fields': {u'@timestamp': u'2014-08-07T12:36:00.200Z',
   u'path': u'app1.log'}}]

Les seules choses qui me tiennent à cœur sont l'obtention de l'horodatage et du chemin d'accès pour chaque hit.

res['hits']['hits'][0]['fields']
Out[48]: 
{u'@timestamp': u'2014-08-07T12:36:00.086Z',
 u'path': u'app1.log'}

Je ne peux pas pour la vie de savoir qui obtenir ce résultat, dans une base de données dans des pandas. Donc, pour les 2 résultats que je suis retourné, je m'attendrais à une base de données comme.

   timestamp                   path
0  2014-08-07T12:36:00.086Z    app1.log
1  2014-08-07T12:36:00.200Z    app2.log
21
Justin S

Il y a un joli jouet appelé pd.DataFrame.from_dict que vous pouvez utiliser dans une situation comme celle-ci:

In [34]:

Data = [{u'_id': u'a1XHMhdHQB2uV7oq6dUldg',
      u'_index': u'logstash-2014.08.07',
      u'_score': 1.0,
      u'_type': u'logs',
      u'fields': {u'@timestamp': u'2014-08-07T12:36:00.086Z',
       u'path': u'app2.log'}},
     {u'_id': u'TcBvro_1QMqF4ORC-XlAPQ',
      u'_index': u'logstash-2014.08.07',
      u'_score': 1.0,
      u'_type': u'logs',
      u'fields': {u'@timestamp': u'2014-08-07T12:36:00.200Z',
       u'path': u'app1.log'}}]
In [35]:

df = pd.concat(map(pd.DataFrame.from_dict, Data), axis=1)['fields'].T
In [36]:

print df.reset_index(drop=True)
                 @timestamp      path
0  2014-08-07T12:36:00.086Z  app2.log
1  2014-08-07T12:36:00.200Z  app1.log

Montrez-le en quatre étapes:

1, lisez chaque élément de la liste (qui est une dictionary) dans une DataFrame

2, nous pouvons mettre tous les éléments de la liste dans un grand DataFrame de concat leur rangée, puisque nous ferons l’étape 1 pour chaque article, nous pouvons utiliser map pour le faire.

3, nous accédons ensuite aux colonnes marquées avec 'fields'

4, nous souhaitons probablement faire pivoter les DataFrame 90 degrés (transposition) et reset_index si nous voulons que l’index soit la séquence int par défaut.

enter image description here

10
CT Zhu

Ou vous pouvez utiliser la fonction json_normalize des pandas:

from pandas.io.json import json_normalize
df = json_normalize(res['hits']['hits'])

Et puis filtrer le résultat dataframe par noms de colonnes

20
Brown nightingale

Mieux encore, vous pouvez utiliser la fantastique bibliothèque pandasticsearch:

from elasticsearch import Elasticsearch
es = Elasticsearch('http://localhost:9200')
result_dict = es.search(index="recruit", body={"query": {"match_all": {}}})

from pandasticsearch import Select
pandas_df = Select.from_dict(result_dict).to_pandas()
10
John D

J'ai testé toutes les réponses pour en vérifier les performances et j'ai constaté que l'approche pandasticsearch est de loin la plus rapide:

tests:

test1 (en utilisant from_dict)

%timeit -r 2 -n 5 teste1(resp)

10,5 s ± 247 ms par boucle (moyenne ± écart type de 2 courses, 5 boucles chacune)

test2 (en utilisant une liste)

%timeit -r 2 -n 5 teste2(resp)

2,05 s ± 8,17 ms par boucle (moyenne ± écart type de 2 cycles, 5 boucles chacun)

test3 (en utilisant import pandasticsearch comme pdes)

%timeit -r 2 -n 5 teste3(resp)

39,2 ms ± 5,89 ms par boucle (moyenne ± écart type de 2 courses, 5 boucles chacune)

test4 (à partir de pandas.io.json import json_normalize)

%timeit -r 2 -n 5 teste4(resp)

387 ms ± 19 ms par boucle (moyenne ± écart type de 2 passages, 5 boucles chacun)

J'espère que cela peut être utile pour n'importe qui

CODE:

index = 'teste_85'
    size = 10000
    fields = True
    sort = ['col1','desc']
    query = 'teste'
    range_gte = '2016-01-01'
    range_lte = 'now'
    resp = esc.search(index = index,
                        size = size,
                        scroll = '2m',
                        _source = fields,
                        doc_type = '_doc',
                        body = {
                            "sort" : { "{0}".format(sort[0]) : {"order" : "{0}".format(sort[1])}},
                            "query": {
                                    "bool": {
                                    "must": [
                                        { "query_string": { "query": "{0}".format(query) } },
                                        { "range": { "anomes": { "gte": "{0}".format(range_gte), "lte": "{0}".format(range_lte) } } },
                                    ]
                                    }
                                }
                                })

    def teste1(resp):
        df = pd.DataFrame(columns=list(resp['hits']['hits'][0]['_source'].keys()))
        for hit in resp['hits']['hits']:
            df = df.append(df.(from_dicthit['_source'], orient='index').T)
        return df

    def teste2(resp):
        col=list(resp['hits']['hits'][0]['_source'].keys())
        for hit in resp['hits']['hits']:
            df = pd.DataFrame(list(hit['_source'].values()), col).T
        return df

    def teste3(resp):
        df = pdes.Select.from_dict(resp).to_pandas()
        return df

    def teste4(resp):
        df = json_normalize(resp['hits']['hits'])
        return df
3
Erick Storck

Voici un peu de code que vous pourriez trouver utile pour votre travail. C’est simple et extensible, mais cela m’a fait gagner beaucoup de temps lorsque je ne fais que "saisir" des données à analyser d’ElasticSearch. 

Si vous voulez juste récupérer toutes les données d'un index et d'un doc_type donnés de votre localhost, vous pouvez faire:

df = ElasticCom(index='index', doc_type='doc_type').search_and_export_to_df()

Vous pouvez utiliser n'importe lequel des arguments que vous utiliseriez habituellement dans elasticsearch.search () ou spécifier un hôte différent. Vous pouvez également choisir d'inclure ou non le _id et spécifier si les données se trouvent dans '_source' ou dans 'champs' (il essaie de deviner). Il essaie également de convertir les valeurs de champ par défaut (mais vous pouvez le désactiver).

Voici le code:

from elasticsearch import Elasticsearch
import pandas as pd


class ElasticCom(object):

    def __init__(self, index, doc_type, hosts='localhost:9200', **kwargs):
        self.index = index
        self.doc_type = doc_type
        self.es = Elasticsearch(hosts=hosts, **kwargs)

    def search_and_export_to_dict(self, *args, **kwargs):
        _id = kwargs.pop('_id', True)
        data_key = kwargs.pop('data_key', kwargs.get('fields')) or '_source'
        kwargs = dict({'index': self.index, 'doc_type': self.doc_type}, **kwargs)
        if kwargs.get('size', None) is None:
            kwargs['size'] = 1
            t = self.es.search(*args, **kwargs)
            kwargs['size'] = t['hits']['total']

        return get_search_hits(self.es.search(*args, **kwargs), _id=_id, data_key=data_key)

    def search_and_export_to_df(self, *args, **kwargs):
        convert_numeric = kwargs.pop('convert_numeric', True)
        convert_dates = kwargs.pop('convert_dates', 'coerce')
        df = pd.DataFrame(self.search_and_export_to_dict(*args, **kwargs))
        if convert_numeric:
            df = df.convert_objects(convert_numeric=convert_numeric, copy=True)
        if convert_dates:
            df = df.convert_objects(convert_dates=convert_dates, copy=True)
        return df

def get_search_hits(es_response, _id=True, data_key=None):
    response_hits = es_response['hits']['hits']
    if len(response_hits) > 0:
        if data_key is None:
            for hit in response_hits:
                if '_source' in hit.keys():
                    data_key = '_source'
                    break
                Elif 'fields' in hit.keys():
                    data_key = 'fields'
                    break
            if data_key is None:
                raise ValueError("Neither _source nor fields were in response hits")

        if _id is False:
            return [x.get(data_key, None) for x in response_hits]
        else:
            return [dict(_id=x['_id'], **x.get(data_key, {})) for x in response_hits]
    else:
        return []
1
thorwhalen

Pour tous ceux qui rencontrent cette question aussi .. @CT Zhu a une bonne réponse, mais je pense que c'est un peu démodé. mais lorsque vous utilisez le package elasticsearch_dsl. Le résultat est un peu différent. Essayez ceci dans ce cas:

# Obtain the results..
res = es_dsl.Search(using=con, index='_all')
res_content = res[0:100].execute()
# convert it to a list of dicts, by using the .to_dict() function
res_filtered = [x['_source'].to_dict() for x in res_content['hits']['hits']]

# Pass this on to the 'from_dict' function
A = pd.DataFrame.from_dict(res_filtered)
1
zwep