YOGYUI

Benford's Law (벤포드의 법칙) 본문

Data Analysis/General

Benford's Law (벤포드의 법칙)

요겨 2021. 2. 23. 20:24
반응형

몇달전에 Netflix에서 방영하는 오리지널 다큐멘터리 "커넥티드: 세상을 잇는 과학" (영문명 connected: The Hidden Science of Everything) 중 4화 '수의 법칙'을 보고 충격을 받은 적이 있어 그 내용을 정리해보고자 한다

※ 6화 모두 엄청 재밌다, 강력 추천! 포스트 작성한다고 2번을 다시 돌려봤다

4화 '수의 법칙', 출처: Netflix

제목인 '수의 법칙' 답게 하나의 수학 공식(법칙?)에 대한 내용을 다루고 있는데, 법칙의 이름은 바로

'벤포드의 법칙' (Benford's Law)

내 나름대로는 수학이나 공학 지식이 풍부하다고 자부하는데 이 법칙은 완전히 처음 접해봤는데다가 실생활에서 광범위하게 활용된다고 하니 꽤 놀라웠다

 

내가 이해한대로 설명하면 장황해질 수 있으니 위키피디아의 정의를 그대로 옮겨적어본다


벤포드의 법칙(Benford's law)은 실세계에서 존재하는 많은 수치 데이터의 10진법 값에서 수의 첫째 자리의 확률 분포를 관찰한 결과, 첫째 자리 숫자가 작을 확률이 크다는 법칙이다. 벤포드의 법칙을 따르는 데이터 집합에 등장하는 수들의 첫째 자리가 1일 확률은 약 30%인 데 반해, 9가 첫째 자리로 등장할 확률은 5% 정도밖에 되지 않는다. 만약 1부터 9까지의 숫자가 수의 맨 앞자리에 등장할 확률이 균등분포를 따른다면, 각 숫자는 약 11.1%의 확률로 맨 앞자리에 등장하여야 할 것이다. 벤포드의 법칙은 또한 수의 둘째 이후 자리의 확률 분포나 숫자 조합에 대한 확률 분포도 예측할 수 있다.
벤포드의 법칙은 굉장히 다양한 종류의 데이터에 적용된다. 예를 들어, 전기요금 고지서, 도로명 주소, 주식 가격, 주택 가격, 인구수, 사망률, 강의 길이, 물리 상수와 수학 상수 등 다양한 데이터에 등장하는 수들이 벤포드의 법칙을 따른다.
이 법칙의 이름은 물리학자 프랭크 벤포드의 이름을 따서 지어졌다. 벤포드는 1938년에 "이례적인 숫자들에 관한 법칙"(The Law of Anomalous Numbers)이라는 논문에서 처음 벤포드의 법칙을 언급했다. 그러나 사실 1881년에 사이먼 뉴컴도 같은 법칙을 이야기한 적이 있다.

(출처: 위키피디아)


정리하자면 숫자로 구성된 데이터셋이 주어졌을 때 숫자들의 첫째 자리 수들을 따로 모아 분포를 조사해보면 균등분포를 따르지 않고 '1'로 시작하는 수가 30% 정도를 차지하고 '9'로 시작하는 수는 5% 정도만 차지한다는 법칙이다 (수가 1~9까지 9개이므로 균등분포라면 각각 11.1%의 점유율을 가져야 한다)

 

다큐멘터리에 따르면, 이 법칙을 먼저 발견한 사이먼 뉴컴은 서고에 꽂혀있는 로그 변환표를 담은 책의 초반부만 닳아있다는 걸 통해 이러한 법칙을 유추하게 되었다고 한다 (예리한데?)

log scale bar, 출처: 위키피디아

 

놀랍게도 벤포드의 법칙에 따르는 첫째 자리 수의 분포에 관한 공식도 존재한다

 

\(P(d)=\log_{10}(d+1)-\log_{10}(d)=\log_{10}({d+1 \over d})=\log_{10}(1+{1 \over d})\)

 

여기서 \(d\)는 첫째자리 수 (leading digit)을 가리킨다

\(d\)에 1부터 9까지 대입시켜보면 \(P(d)\)는 다음과 같이 계산된다

벤포드의 법칙 (첫째자리 수의 분포), 출처: 위키피디아

머리로는 이해하기 쉬운데 가슴으로는 받아들이기 쉽지 않다

대수의 법칙이 통하지 않아서일까? 

 

미대륙의 도시별 인구 분포, 베토벤/바흐/슈베르트의 교향곡 내 음계 지속시간의 분포, 스포츠 (농구 선수의 평생 득점수, 태권도 발차기 수, 크리켓 타자당 득점 수 등), 전세계 마천루들의 높이, 건강 및 보건(암 발생률, 전염병 발병률, 사망 직전 심박 간격) 등 정말 다양한 카테고리의 숫자들이 벤포드의 법칙에 따른 분포를 보인다고 한다 (어메이징)

 

순수 수학계에서는?

피보나치 수나 팩토리얼같은 몇몇 무한 정수 순열(infinite integer sequence)들은 완벽히 벤포드의 법칙을 따른다고 한다

 

벤포드의 법칙은 굉장히 다양한 범위에서 사용되고 있다고 한다

특히 기업의 회계 장부가 벤포드의 법칙을 크게 위반하면 분식같은 부정회계로 의심한다고 한다...

(미국 최대의 회계 스캔들 엔론의 2000년도 회계 장부도 벤포드의 법칙을 크게 벗어났다네?)

미국 대선의 집표 결과를 분석해 투표 조작 여부에 대한 분석에도 활용되었다고 하며, 소셜네트워크 서비스 계정의 친구 수나 팔로워 수를 분석해 봇(bot)계정인지 여부도 판별할 수 있다고 한다 (...정말??)

가장 재밌는 건, 최근 문제가 되고 있는 딥페이크 영상을 걸러낼 때도 쓰인다고 한다 (이미지나 영상 내 모든 픽셀들 값의 첫번째 수의 분포가 원본일 경우 벤포드의 법칙을 따른다고 한다)

 

[관련 링크]

[Math in Biz] 숫자 첫번째 자리에 규칙이?…회계부정 잡아낸 '벤포드의 법칙'

숫자 사기 잡아내는 ‘벤포드의 법칙’

TESTING BITCOIN WITH BENFORD’S LAW

 

다큐멘터리만 보면 아주 그냥 우주 최강 만능의 법칙같아 보이지만, 벤포드의 법칙을 따르지 않는 시스템도 많다고 한다

출처: 위키피디아

 

이래저래 구글링해보다가 가장 눈길을 사로잡은건 '국가별 코로나19 확진자 수'를 분석해 확진자 수를 조작했을 가능성이 높은 국가들을 언급한 논문이었다 (논문 내에서는 국가명을 명시하고 있는데, 조작했는지 여부는 밝혀지지 않았으니 관심있는 사람은 논문을 직접 읽어보도록 하자)

이런 기사도 있다

Benford's Law: Which Countries Are Committing COVID Data Fraud?

참고로, 굉장히 의심스러웠던 중국의 경우 벤포드의 법칙을 따른다고 한다...ㅋㅋㅋ

China's COVID-19 data matches Benford's Law like U.S. and Italy: Researchers

 

한국의 경우 논문에서는 다뤄지지 않았으니 직접 분석해보도록 하자

국내 코로나 감염 현황의 데이터를 획득하는 과정에 대해서는 링크에서 다뤘으니 참고하면 된다

 

파이썬을 활용해서 국내 코로나19 일일 신규확진자 수에 대해서 벤포드 법칙을 얼마나 따르는지 조사해보자

df = getAllCovid19Data()
In [1]: df[['statedt', 'decide_new_daily']]
Out[1]:
       statedt  decide_new_daily
0   2021-02-23               357
1   2021-02-22               332
2   2021-02-21               416
3   2021-02-20               448
4   2021-02-19               561
5   2021-02-18               621
6   2021-02-17               621
7   2021-02-16               457
8   2021-02-15               343
9   2021-02-14               326
10  2021-02-13               362
11  2021-02-12               403
12  2021-02-11               504
13  2021-02-10               444
14  2021-02-09               303
15  2021-02-08               288
16  2021-02-07               371
17  2021-02-06               393
18  2021-02-05               370
19  2021-02-04               451
20  2021-02-03               467
21  2021-02-02               335
22  2021-02-01               305
23  2021-01-31               355
24  2021-01-30               453
25  2021-01-29               469
26  2021-01-28               497
27  2021-01-27               559
28  2021-01-26               349
29  2021-01-25               437
..         ...               ...
363 2020-03-02               476
364 2020-03-01               586
366 2020-02-29               813
368 2020-02-28               571
370 2020-02-27               505
372 2020-02-26               284
374 2020-02-25               144
376 2020-02-24               231
378 2020-02-23               169
380 2020-02-22               229
382 2020-02-21               100
385 2020-02-20                53
387 2020-02-19                20
389 2020-02-18                 1
392 2020-02-17                 1
394 2020-02-16                 1
396 2020-02-15                 0
398 2020-02-14                 0
400 2020-02-13                 0
402 2020-02-12                 0
404 2020-02-11                 1
406 2020-02-10                 0
408 2020-02-09                 3
410 2020-02-08                 0
415 2020-02-07                 1
417 2020-02-06                 4
418 2020-02-05                 1
419 2020-02-05                18
420 2020-02-04                 0
423 2020-01-01                 0

[387 rows x 2 columns]

신규확진자 수 중 0인 레코드를 제외한 뒤 문자열로 변환

temp1 = df[df['decide_new_daily'] > 0]['decide_new_daily'].values
temp2 = [str(x) for x in temp1]

문자열로 변환된 리스트들 각 요소의 첫번째 자리만 정수로 변환

temp3 = [int(x[0]) for x in temp2]
In [2]: temp3[:10]
Out[2]: [3, 3, 4, 4, 5, 6, 6, 4, 3, 3]

이제 1 ~ 9 까지 카운트해주면 된다

iteration해도 되고, numpy의 histogram 함수를 써도 된다

import numpy as np

hist_result_1 = [sum([y == x for y in temp3]) for x in range(1, 10)]  # iteration
hist_result_2 = np.histogram(temp3, bins=np.arange(1, 11))[0]  # numpy histogram
In [3]: hist_result_1
Out[3]: [97, 43, 65, 48, 40, 32, 17, 19, 18]

In [4]: hist_result_2
Out[4]: array([97, 43, 65, 48, 40, 32, 17, 19, 18], dtype=int64)

In [5]: hist_result_1 == hist_result_2
Out[5]: array([ True,  True,  True,  True,  True,  True,  True,  True,  True])

벤포드의 법칙 분포값을 계산하는 함수도 구현

def getBenfordDistribution():
    return [np.log10((x + 1) / x) for x in range(1, 10)]
In [6]: getBenfordDistribution()
Out[6]:
[0.3010299956639812,
 0.17609125905568124,
 0.12493873660829993,
 0.09691001300805642,
 0.07918124604762482,
 0.06694678963061322,
 0.05799194697768673,
 0.05115252244738129,
 0.04575749056067514]

히스토그램을 정규화(normalization)한 뒤 벤포드 법칙 분포와 함께 플롯해보자

import matplotlib.pyplot as plt

hist_norm = hist_result_2 / sum(hist_result_2)
benford = getBenfordDistribution()

xrange = range(1, 10)
plt.plot(xrange, np.array(benford) * 100, c='red', ls='--', marker='o')
plt.bar(xrange, hist_norm * 100)
plt.xlabel('Leading Digit')
plt.ylabel('Percentage')
plt.xticks(xrange)
plt.title('Korea - COVID 19 Daily')

일일 신규 확진자 첫째자리 분포 vs 벤포드 분포

흠... 시각화 결과만 놓고봐서는 명확하게 벤포드 법칙을 따른다고 결론짓기는 힘들거같다

(확실히 숫자 '1'이 25.5% 수준으로 가장 많이 나왔고 7, 8, 9 같은 큰 수들은 점유율이 작긴 하다)

 

통계적으로 분석하려면 '카이제곱 검정'을 해보면 된다 (데이터가 특정 분포를 따르는지를 판단하는 적합도 검정, goodness of fit test)

카이제곱 검정은 scipy 라이브러리를 활용하면 코드 한줄로 가능하다

from scipy.stats import chisquare

chisq_test = chisquare(hist_norm, f_exp=benford)
In [7]: chisq_test
Out[7]: Power_divergenceResult(statistic=0.07191946711791768, pvalue=0.9999999323031546)

p-value가 0.99로 1에 가까워 귀무가설(\(H_{0}\): 두 분포가 동일하다)을 기각하지 못한다

(default 계산은 95% 신뢰구간에 대한 검정이지만, 유의 수준을 0.01로 설정해도 결과는 마찬가지)

즉, 한국의 코로나-19 일일 신규확진자 수의 첫번째 자리 수의 분포는 벤포드 법칙의 분포를 따른다고 할 수 있다 

 

재미있는 분석 결과다

 

전세계 모든 국가들의 확진자 현황 데이터를 가져와서 (아마 캐글에 데이터셋이 널려있지 않을까?) 국가별로 일일 신규 확진자 수가 벤포드 법칙을 따르는지를 분석해보고, 위 논문에서 언급한 '조작이 의심되는 국가'와 일치하는지 조사해보면 더 흥미로울 것 같다

다음번 포스트에서 직접 해보도록 하자!

 

끝~!

 

반응형
Comments