About

이번 블로그에서는 이전 블로그(Elasticsearch 기본 개념과 python 패키지 elasticsearch-dsl 기본 사용법)에서 설명하지 못한 regex를 이용한 Search방법과 Aggregation에 대해서 보충 설명하고자 한다. Elasticsearch가 처음이시거나 기본 개념을 아시고자 하시는 분은 위 링크에서 확인하고 오는 것을 추천한다.

Implementation

먼저 아래와 같이 모듈들을 임포트하고, es에는 elasticsearch 객체를 생성하는 것을 아래의 코드들에서도 공통되게 사용할 것이다.

import elasticsearch
from elasticsearch_dsl import Search, Q, Index

es = elasticsearch.Elasticsearch('localhost:9200')

Regexp filter

q = Q(
    'bool',
    should=[
        Q('regexp', name='.*수{1}'),
        Q('regexp', name='.*맛집.*'),
    ],
    minimum_should_match=1)
s = Search(using=es, index='index_name').query(q)
res = s.execute()

Q 모듈을 이용하여 regexp search가 가능하다. 가장 기본적인 syntax들만 살펴보자. 아래 내용은 Elasticsearch Regexp-syntax에서 소개한 일부를 번역한 것이다. 자세하게 알고싶으신 분은 위의 링크로 들어가서 확인하도록 하자.

1. .

하나의 어떤 글자든 될 수 있다.
예) ab. # matches ‘abc’, ‘abd’, ‘abs’ … etc

2. ?

앞글자가 0번 혹은 1번 등장하는 경우를 찾아온다.
예) abc?라면 c가 나오거나 안나오거나 둘 다 가져온다. # matches ‘ab’, ‘abc’

3. +

앞글자가 1번이상으로 등장하는 경우를 찾아온다.
예) abc+ # matches ‘abc’, ‘abccccc’ … etc

4. *

앞글자가 0번 혹은 1번이상 등장하는 경우를 찾아온다.
예) abc* # matches ‘ab’, ‘abccc’ … etc

5. {}

앞글자가 등장할 수 있는 범위를 설정할 수 있다.
예) c{2} # matches cc
c{2, 4} # matches cc, ccc, cccc

이제 위의 내용으로 정리해보자면,

'.*수{1}'

는 앞에 어떤 글자들이 나오거나 안나와도 관계없으나 “수”는 한번만 나오는 text를 반환하는 코드다.

'.*맛집.*'

은 앞과 뒤에 어떤 글자든 있거나 없어도 관계없고, “맛집”이 등장한 text를 반환하는 코드다.

하지만 elasticsearch에서 사용할 때는 일반적인 regexp와 차이점이 있다. Inverted index에 저장된 단어들을 하나하나 확인하기 때문에 문장 시작을 의미하는 ^라던가 끝을 의미하는 $와 같은 표현법은 사용하지 못하게 되어있음을 명심하자.

2. Aggregation
s = Search(index='index_name', using=es)
a = A('terms', field='user_name', size=s.count())
a.pipeline('작성자가 답한 문항 정보', 'terms', field='question_id')
s.aggs.bucket('user history', a)
res = s.execute()
buckets = res.aggregations['user history'].to_dict()['buckets']    

나중에 다른 통계기법을 사용하게되면 다시 블로그를 작성하도록하고, 이번 블로그에서는 간단한 통계 정보인 count하는 법에 대해서만 알아보도록 하겠다.

참고로 이번 Index는 사용자들의 설문조사 답변들이 등록된 것이라고 가정하고 설명하도록 하겠다.

{
  'user_name': '홍길동',
  'item_id': 1,
  'question_id': 1,
  'question': '해당 아이템의 전체적인 만족도는 몇 점인가요?',
  'answer': 5.0
},
{
  'user_name': '홍길동',
  'item_id': 1,
  'question_id': 2,
  'question': '해당 아이템의 가격 만족도는 몇 점인가요?',
  'answer': 5.0
}

예를 들면 위와 같은 형식의 데이터들이 있다.

이제 해석을 해보자. 먼저

a = A('terms', field='user_name', size=s.count())

user_name 필드에서 unique한 user들이 몇번 등장했는지 return한다. size는 return하는 총 데이터 개수이다. 여기서는 전체를 반환해달라는 의미로 s.count()를 사용했다. 반환하는 값은

{
  'key': '홍길동',
  'doc_count': 1275
}

과 같은 방식이다. 이는 user_name 필드가 홍길동인 데이터 row가 총 1275번 등장했다는 의미다.

a.pipeline('작성자가 답한 문항 정보', 'terms', field='question_id')

pipeline을 이용하게되면 위에서 가져온 정보내에서 다시 한번 aggregation을 진행하게 된다. 위의 코드는 홍길동이 답한 question_id들의 통계를 반환한다.

s.aggs.bucket('user history', a)

이는 위에서 실행한 모든 통계에 대한 정보를 user history라는 key로 감싸서 값들을 return해달라는 의미다.

그럼 반환하는 값은 아래와 같은 형식이다.

{
  'key': '홍길동',
  'doc_count': 1275,
  'uname2uid': {
    'doc_count_error_upper_bound': 0,
    'sum_other_doc_count': 0,
    'buckets': [
      {'key': 1, 'doc_count': 32},
      {'key': 2, 'doc_count': 13},
    ]
  }
}

Conculusion

필자가 회사에 적용하면서 배운점들을 작성해봤다. 만약 조금 더 난이도 있는 작업들을 하게된다면 관련해서 또 작성하도록 하겠다. 만약 도움이 되었다면 널리 퍼뜨려주시길!