web-dev-qa-db-ja.com

ElasticSearch結果からのDataFrameの作成

ElasticSearchへの非常に基本的なクエリの結果を使用して、パンダでDataFrameを構築しようとしています。必要なデータを取得していますが、適切なデータフレームを構築する方法で結果をスライスすることが問題です。私は本当に、各結果のタイムスタンプとパスを取得することだけに関心があります。いくつかの異なるes.searchパターンを試しました。

コード:

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'))

これにより、4つのデータチャンクが得られます。 [u'hits '、u'_shards'、u'took '、u'timed_out']。私の結果はヒットの中にあります。

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'}}]

私が気にしているのは、タイムスタンプと各ヒットのパスを取得することだけです。

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

私の人生では、誰がその結果を得るのか、パンダのデータフレームに入れることができません。したがって、返された2つの結果については、データフレームのようなものを期待します。

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

このような状況で使用できるpd.DataFrame.from_dictという素敵なおもちゃがあります。

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

4つのステップで示します。

1、リストの各項目(dictionary)をDataFrameに読み取ります

2、リスト内のすべての項目を行単位で大きなDataFrame by concatに入れることができます。各項目に対して手順1を実行するため、map それを行うには。

3、次に'fields'のラベルが付いた列にアクセスします

4、インデックスをデフォルトのDataFrameシーケンスにしたい場合は、intを90度回転(転置)し、reset_indexを回転したい場合があります。

enter image description here

11
CT Zhu

または、pandasのjson_normalize関数を使用することもできます。

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

そして、列名で結果データフレームをフィルタリングします

23

さらに良いことに、素晴らしい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()
14
John D

すべての回答のパフォーマンスをテストしたところ、pandasticsearchアプローチが大幅に高速であることがわかりました。

テスト:

test1(from_dictを使用)

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

ループあたり10.5秒±247ミリ秒(2回の実行の平均±標準偏差、それぞれ5ループ)

test2(リストを使用)

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

ループあたり2.05秒±8.17ミリ秒(2回の実行の平均±標準偏差、それぞれ5ループ)

test3(pandaとしてpandasticsearchを使用)

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

ループごとに39.2 ms±5.89 ms(2つの実行の平均±標準偏差、各5ループ)

test4(pandas.io.json import json_normalizeから使用)

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

ループあたり387 ms±19 ms(2回の実行の平均±標準偏差、各5ループ)

私はそれが誰にとっても役に立つことを願っています

コード:

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_dict(hit['_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
5
Erick Storck

作業に役立つコードを次に示します。シンプルで拡張可能ですが、ElasticSearchから一部のデータを「取得」して分析するだけで、多くの時間を節約できました。

ローカルホストの特定のインデックスとdoc_typeのすべてのデータを取得したい場合は、次のようにすることができます。

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

Elasticsearch.search()で通常使用する任意の引数を使用するか、別のホストを指定できます。 _idを含めるかどうかを選択し、データが「_source」または「fields」のどちらにあるかを指定することもできます(推測しようとします)。また、デフォルトでフィールド値を変換しようとします(ただし、オフにすることもできます)。

これがコードです:

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

この質問に遭遇した人のためにも.. @CT Zhuは素晴らしい答えを持っています、 しかし、それは少し時代遅れだと思います。 ただし、elasticsearch_dslパッケージを使用している場合。結果は少し異なります。その場合はこれを試してください:

# 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

elasticsearch_dslドキュメントを検索したり、IDで取得したりできます。

例えば

from elasticsearch_dsl import Document

# retrieve document whose _id is in the list of ids
s = Document.mget(ids,using=es_connection,index=myindex)

または

from elasticsearch_dsl import Search

# get (up to) 100 documents from a given index
s = Search(using=es_connection,index=myindex).extra(size=100)

次に、DataFrameを作成し、データフレームインデックスでelasticsearch IDを使用する場合は、次のようにします。

df = pd.DataFrame([{'id':r.meta.id, **r.to_dict()} 
                            for r 
                            in s.execute()]).set_index('id',drop=True)
0
HerrIvan

リクエストがElasticsearchから10,000を超えるドキュメントを返す可能性がある場合は、Elasticsearchのスクロール機能を使用する必要があります。この関数のドキュメントと例を見つけるのはかなり難しいので、完全に機能する例を提供します。

import pandas as pd
from elasticsearch import Elasticsearch
import elasticsearch.helpers


es = Elasticsearch('http://localhost:9200')

body={"query": {"match_all": {}}}
results = elasticsearch.helpers.scan(es, query=body, index="my_index")
df = pd.DataFrame.from_dict([document['_source'] for document in results])

「my_」で始まるフィールドを編集して、独自の値に対応させる

0
John D