Dec 22nd, 2021 [KO] Eland: 데이터 분석을 위한 Python client

파이썬(python) 은 데이터 분석을 위한 가장 보편적인 프로그래밍 언어로 자리 잡았습니다. 데이터 분석을 위해
NumPy, Pandas, SciPy, scikit-learn 등 파이썬의 주요 패키지를 주로 사용합니다.

특히, 판다스(Pandas) 는 데이터 분석에 있어서 가장 많이 사용하는 피이썬 라이브러리 중 하나 입니다. 데이터를 분석할 때 NumPy(넘파이), Pandas(판다스)로 데이터를 가공한 후 분석 라이브러리인 statsmodels(통계 분석), scikit-learn(머신러닝)를 활용합니다.

판다스의 핵심인 데이터프레임(DataFrame)행(Row) x 열(Column) 로 구성된 2차원의 표 형식 으로 데이터를 다루기 때문에 훨씬 더 쉽게 데이터를 핸들링 할 수 있습니다.

그럼, 엘라스틱서치에서도 데이터프레임(DataFrame)을 활용해 데이터 분석을 할 수 있을까요? :thinking:

네, Eland를 통해서 가능합니다 :smiley:

Eland를 사용해 파이썬으로 어떻게 데이터 분석 및 머신 러닝에 활용할 수 있는지 소개하겠습니다.


Overview

Eland 는 오픈 소스 파이썬 라이브러리이며 데이터프레임(DataFrame)머신러닝 을 위한 파이썬 클라이언트(Python client) 툴킷(toolkit) 입니다.

지원 및 권장하는 버전

  • 파이썬 3.7+, Pandas 1.3
  • Elasticsearch cluster 7.11+, 권장되는 버전은 7.14+

Why Eland?

Eland의 장점은 크게 3가지가 있습니다.

Eland 장점

  • 데이터를 메모리에 보관하지 않고도 대용량의 데이터 셋을 탐색할 수 있다.
  • 데이터를 Elasticsearch에 업로드할 수 있다.
  • DSL Query를 사용하지 않고 보다 간편하게 검색 및 집계를 할 수 있다.

Data Frame

실습에 사용된 Elastic Stack, 주요 패키지 버전은 다음과 같습니다.

  • Elastic Cloud: Deployment version 7.14.1
  • 운영체제: macOS Big Sur (버전 11.6)
  • 파이썬: 3.7.11
  • Pandas: 1.3.2
  • Eland: 8.0.0b1
  • NumPy: 1.21.2
  • matplotlib: 3.5.1
  • seaborn: 0.11.2

1. Elasticsearch Cloud에서 Deployment 생성

  • Elastic Cloud를 통해 Elastic Stack을 구성합니다. Elastic Cloud는 14일 동안 무료로 사용 할 수 있으며 신용카드 등록이 필요 없습니다.

2. Kibana에서 Eland API에 사용할 User를 생성

  • Kibana > Open 클릭합니다.

  • Managment > Stack Management > Security > Users > Create User 클릭합니다.

  • UsernamePasswordelastic 로 설정했습니다.

3. 데이터 추가

  • Kibana > Add data > Sample data > Sample flight data > Add data

  • Kibana > Management > Stack Management > Kibana > Index Patterns를 보면 방금 추가한 데이터 kibana_sample_data_flights 를 확인할 수 있습니다.

4. Authentication

  • 먼저, 아래 두 값을 확인합니다.
  • ELASTIC_CLOUD_ID: Cloud ID
  • ELASTIC_CLOUD_PASSWORD: elastic

ELASTIC_CLOUD_ID 값은 여기서 확인할 수 있으며 ELASTIC_CLOUD_PASSWORD는 이전의 만들었던 elastic 를 입력하면 됩니다. (예제이기 때문에 패스워드를 직접 입력했습니다. 운영 환경에서 사용시 직접 패스워드를 입력하는 방법이 아닌 다른 방법을 고려해야 합니다.)

5. Elasticsearch cluster 연결

  • 아래의 코드를 작성한 뒤 실행시킵니다. 다른 패키지가 설치되어 있지 않다면 pip install 모듈을 통해 설치를 진행합니다.
import eland as ed
import numpy as np
from elasticsearch import Elasticsearch
import matplotlib.pyplot as plt


def json(x):
    import json
    print(json.dumps(x, indent=2, sort_keys=True))
    
ELASTIC_CLOUD_ID = "Jihyun_Eland:YXNpYS1ub3J0aGVhc3QzLmdjcC5lbGFzdGljLWNsb3VkLmNvbSQyZjhjMjdkNmY4YjU0YWU3YTgxNzE0ZjQ1OGVhZDBmZSQxZTQwMjIxZjZkMjU0MjA****4N2Q0OTgzMjUyZQ=="
ELASTIC_CLOUD_PASSWORD = "elastic"

es = Elasticsearch(
  cloud_id=ELASTIC_CLOUD_ID,
  basic_auth=("elastic", ELASTIC_CLOUD_PASSWORD)
)

print(es.info())
  • Elastic cloud 인스턴스에 정상적으로 연결되었습니다.
{'name': 'instance-0000000001', 'cluster_name': '2f8c27d6f8b54ae7a81714f458ead0fe', 'cluster_uuid': 'XYm66sAvRAymR6kL3zUu3A', 'version': {'number': '7.14.1', 'build_flavor': 'default', 'build_type': 'docker', 'build_hash': '66b55ebfa59c92c15db3f69a335d500018b3331e', 'build_date': '2021-08-26T09:01:05.390870785Z', 'build_snapshot': False, 'lucene_version': '8.9.0', 'minimum_wire_compatibility_version': '6.8.0', 'minimum_index_compatibility_version': '6.0.0-beta1'}, 'tagline': 'You Know, for Search'}

6. 데이터 프레임(DataFrame)

  • 이제 eland 데이터 프레임(DataFrame)을 사용하여 kibana_sample_data_flight 인덱스를 가져오겠습니다.
ed_flights = ed.DataFrame(es, es_index_pattern="kibana_sample_data_flights")
ed_flights.head()

  • 행(Row) x 열(Column)로 구성된 2차원의 표 형식으로 손쉽게 데이터를 확인할 수 있습니다. 데이터 프레임과 엘라스틱서치의 구조를 비교해보면 아래와 같습니다.

Mapping Data Frame Concepts to Elasticsearch

  • Data Frame -> Indices
  • Row -> Document
  • Column -> Field
  • Dtype -> Field Type
  • Index -> Doc ID + Sort Order

Eland 장점 1) 데이터를 메모리에 보관하지 않고도 대용량의 데이터 셋을 탐색

  • info() 함수를 통해 Columns, rows, null 값, Dtype 등을 확인할 수 있습니다. 특히, memory usage를 살펴보면 96 bytes를 사용하고 있습니다.
ed_flights.info()

  • Eland 데이터 프레임에 저장된 ed_flights 데이터셋을 csv 파일로 저장한 뒤 판다스(Pandas) read_csv() 사용해 df_flights 데이터 프레임에 저장합니다. 그리고 메모리 사용량을 비교해보겠습니다.
ed_flights.to_csv("kibana_sample_data_flights_2.csv", index=False, mode='w')
df_flights = pd.read_csv("kibana_sample_data_flights_2.csv")
df_flights.info()

  • Eland가 훨씬 적은 메모리를 사용하고 있다는 것을 확인할 수 있습니다.

  • Eland API를 통해 모든 데이터와 작업은 Elasticsearch 클러스터에서 실행되고 결괏값은 데이터 프레임 형식으로 표현됩니다.

Eland 장점 2) 데이터를 Elasticsearch에 업로드

  • 판다스 데이터 프레임을 Eland 데이터 프레임으로 변환하여 Elasticsearch에 인덱스로 저장할 수 있습니다. 데이터 프레임 df_flights를 연결된 Elastics Cloud 인스턴스에 kibana_sample_data_flights_2로 저장해보겠습니다.
# Elastic cloud 인스턴스에 데이터 업로드
df = ed.pandas_to_eland(
    pd_df=df_flights,
    es_client=es,

    # Elastic cloud 인스턴스에 저장될 인덱스
    es_dest_index="kibana_sample_data_flights_2",

    # 특정 컬럼에 대한 유형 재정의(여기서는 생략)
    #  es_type_overrides={
    #  },
    # 인덱스가 존재하는 경우
    es_if_exists="replace",

    # 데이터가 인덱싱될 때까지 기다립니다.
    es_refresh=True,
)
df.info()

7. 탐색적 데이터 분석(EDA)

  • 탐색적 데이터 분석(EDA, Exploratory Data Analysis)을 해보겠습니다.
  • 판다스에서 자주 사용하는 describe() 함수를 통해 평균, 표준표차, 최솟값, 최댓값 등 기술 통곗값을 확인할 수 있습니다.
ed_flights.describe().T

  • 또한, 히스토그램을 통해서 데이터에 분포도 또한 살펴볼 수 있습니다.
ed_flights.select_dtypes(include=np.number).hist(figsize=[10,10])
plt.show()

8. Eland Pandas API vs Query DSL 비교

  • 아래의 조건으로 Eland Pandas API와 Query DSL를 사용해 두 방법을 비교해보겠습니다.
ed_flights[(ed_flights.AvgTicketPrice > 900.0) & (ed_flights.Carrier == "Kibana Airlines")].head(10)

GET kibana_sample_data_flights/_search?size=10&sort=_doc:asc
{
  "query": {
    "bool" : {
      "must": [
        {"range": { "AvgTicketPrice": { "gt": 900.0 }}},
        {"term": { "Carrier" : "Kibana Airlines"}}
        ]
    }}
}

Eland 장점 3) DSL Query를 사용하지 않고 보다 간편하게 검색 및 집계를 할 수 있다.

  • Query DSL에 익숙하지 않아도 판다스의 데이터 프레임 사용법을 조금만 익히면 손쉽게 데이터 검색 및 집계를 할 수 있습니다.

TIP: Eland에서 지원하지 않는 기능들은 Pandas로 변환해서 사용할 수 있습니다.

Machine Learning

  • 머신러닝을 공부해 보신 분들은 다들 익숙한 붓꽃(Iris) 데이터 셋으로 eland.ml 에 대해 살펴보겠습니다.

[독립변수(X)]

  • sepal length (cm): 꽃받침 길이
  • sepal width (cm): 꽃받침 너비
  • petal length (cm): 꽃잎 길이
  • petal width (cm): 꽃잎 너비

[종속변수(y)]

  • target: 꽃잎의 종류(Species), Virsicolor / Virginica / Setosa

1. 데이터 전처리

  • 데이터 셋을 불러오고 붓꽃의 종류(Species)는 0인 경우 setosa, 1이면 versicolor, 그 외의 값인 virginica는 2로 분류해서 저장합니다.
# 데이터 셋
from sklearn import datasets

# 머신러닝을 위한 패키지
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split

# 시각화를 위한 패키지
import seaborn as sns

# 경고 메시지 무시
import warnings
warnings.filterwarnings('ignore')

# iris dataset을 불러옵니다.
iris = datasets.load_iris()
df_iris = pd.DataFrame(data=np.c_[iris.data, iris.target], columns= iris.feature_names + ['target'])

# target을 분류
df_iris['target'] = df_iris['target'].apply(lambda x: 'setosa' if 0 == x else ('versicolor' if 1 == x else 'virginica'))
df_iris['target_number'] = iris.target
df_iris.head()

2. 시각화

  • 꽃받침(sepal)의 길이와 너비를 시각화하여 붓꽃들이 어떻게 분류되어 있는지 살펴보겠습니다. 시각화를 위해 seaborn 패키지의 pairplot()을 사용합니다.
sns.pairplot(data=df_iris.loc[:, ['target', 'sepal length (cm)','sepal width (cm)']], hue="target", size=5,
             plot_kws=dict(s=50, linewidth=1))

3. 머신러닝 학습

  • 학습, 테스트 데이터로 분리하고, 테스트 데이터로 학습된 모델의 성능을 확인합니다.
data = iris.data
target = iris.target

# 학습, 테스트 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(data, target, test_size = 0.2, random_state = 1)

# random_state은 동일한 결괏값을 얻기 위해 설정
iris_classifier = DecisionTreeClassifier(random_state = 1)

# 학습: fit()
iris_classifier.fit(X_train, y_train)
print("score : ", iris_classifier.score(X_test, y_test))
  • score : 0.9666666666666667

4. 학습된 모델 테스트

  • 붓꽃의 종류(Species)별로 1개씩 데이터를 추출해 학습된 모델(iris_classifier)을 테스트해 보겠습니다.
df_iris.iloc[[1,70,120]]

from eland.ml import MLModel

# 학습된 모델을 적용
ed_classifier = MLModel.import_model(
  es_client=es,
  model_id="iris-classifier",
  model=iris_classifier,
  feature_names=iris.feature_names,
  es_if_exists="replace"
)

# Elasticsearch API call 로깅
import logging
logger = logging.getLogger("elasticsearch")
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler())

# 이전과 동일한 데이터를 사용하지만 Elasticsearch의 머신러닝 모델 사용
print(ed_classifier.predict(iris.data[[1,70,120]].tolist()))
print(iris.target[[1,70,120]])

logger.handlers = []

  • 학습된 모델(iris_classifier)이 붓꽃의 종류를 정확히 예측한 것을 확인할 수 있습니다.

5. 결괏값 확인

  • Elasticsearch API call 로깅한 결괏값을 확인해보겠습니다. df_iris.iloc[[1,70,120]]의 값을 확인할 수 있습니다.
json({"pipeline":{"processors":[{"inference":{"model_id":"iris-classifier","inference_config":{"classification":{}},"field_map":{}}}]},"docs":[{"_source":{"sepal length (cm)":4.9,"sepal width (cm)":3.0,"petal length (cm)":1.4,"petal width (cm)":0.2}},{"_source":{"sepal length (cm)":5.9,"sepal width (cm)":3.2,"petal length (cm)":4.8,"petal width (cm)":1.8}},{"_source":{"sepal length (cm)":6.9,"sepal width (cm)":3.2,"petal length (cm)":5.7,"petal width (cm)":2.3}}]})

  • 학습된 모델 iris_classifier이 예측한 값(predcted_value)을 확인할 수 있습니다.
json({"docs":[{"doc":{"_index":"_index","_type":"_doc","_id":"_id","_source":{"sepal width (cm)":3.0,"petal width (cm)":0.2,"petal length (cm)":1.4,"sepal length (cm)":4.9,"ml":{"inference":{"prediction_score":1.0,"model_id":"iris-classifier","prediction_probability":1.0,"predicted_value":"0"}}},"_ingest":{"timestamp":"2021-12-16T23:45:47.841383931Z"}}},{"doc":{"_index":"_index","_type":"_doc","_id":"_id","_source":{"sepal width (cm)":3.2,"petal width (cm)":1.8,"petal length (cm)":4.8,"sepal length (cm)":5.9,"ml":{"inference":{"prediction_score":1.0,"model_id":"iris-classifier","prediction_probability":1.0,"predicted_value":"1"}}},"_ingest":{"timestamp":"2021-12-16T23:45:47.84139846Z"}}},{"doc":{"_index":"_index","_type":"_doc","_id":"_id","_source":{"sepal width (cm)":3.2,"petal width (cm)":2.3,"petal length (cm)":5.7,"sepal length (cm)":6.9,"ml":{"inference":{"prediction_score":1.0,"model_id":"iris-classifier","prediction_probability":1.0,"predicted_value":"2"}}},"_ingest":{"timestamp":"2021-12-16T23:45:47.841402925Z"}}}]})


지금까지 배운 내용을 요약하겠습니다.

  • 데이터를 메모리에 보관하지 않고도 대용량에 데이터 셋을 탐색할 수 있다.
  • 데이터를 Elasticsearch에 업로드할 수 있다.
  • DSL Query를 사용하지 않고 보다 간편하게 검색 및 집계를 할 수 있다.

:warning: Eland는 현재 beta 버전이며 8.0 stable 버전을 내년 상반기 release를 준비 중입니다.

Reference

4 Likes

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.