PEP 450 – 표준 라이브러리에 통계 모듈 추가
- Author:
- Steven D’Aprano <steve at pearwood.info>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 01-Aug-2013
- Python-Version:
- 3.4
- Post-History:
- 13-Sep-2013
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 평균, 중앙값, 분산 및 표준 편차와 같은 일반적인 통계 함수를 위한 모듈을 Python 표준 라이브러리에 추가할 것을 제안합니다. http://bugs.python.org/issue18606 도 참조하십시오.
근거
제안된 통계 모듈은 Python 표준 라이브러리에 대한 “배터리 포함” 철학에 기반합니다. Raymond Hettinger와 다른 선임 개발자들은 고급 통계 라이브러리와 임시방편 코드의 중간쯤에 해당하는 품질 높은 통계 라이브러리를 요청해 왔습니다. [1] 평균, 표준 편차 및 기타 통계 함수는 명백하고 유용한 배터리이며, 모든 중등학교 학생에게 익숙합니다. 저렴한 과학용 계산기에도 일반적으로 다음과 같은 여러 통계 함수가 포함되어 있습니다.
- 평균
- 모집단 분산 및 표본 분산
- 모집단 표준 편차 및 표본 표준 편차
- 선형 회귀
- 상관 계수
중등학교 학생을 대상으로 하는 그래프 계산기에는 일반적으로 위의 모든 기능과 더불어 다음 기능 중 일부 또는 전부가 포함되어 있습니다.
- 중앙값
- 최빈값
- 정규분포, t 분포, 카이제곱 분포 및 F 분포에서 확률 변수의 확률을 계산하는 함수
- 평균에 대한 추론
그 밖의 기능도 있습니다 [2]. 마찬가지로 Microsoft Excel, LibreOffice 및 Gnumeric과 같은 스프레드시트 애플리케이션에는 다양한 통계 함수 모음이 포함되어 있습니다 [3].
반면 Python에는 현재 평균과 같은 가장 단순하고 명백한 통계 함수조차 계산할 표준적인 방법이 없습니다. Python에서 통계 함수가 필요한 사용자에게는 두 가지 명백한 해결책이 있습니다.
- numpy 및/또는 scipy를 설치합니다 [4];
- 또는 직접 만든 해결책을 사용합니다.
Numpy는 아마도 가장 많은 기능을 제공하는 해결책이지만, 몇 가지 단점이 있습니다.
- 많은 목적에 비해 지나치게 복잡할 수 있습니다. numpy의 문서에서도 다음과 같이 경고합니다.“numpy에서 어떤 함수를 사용할 수 있는지 알기 어려울 수 있습니다. 이것은 완전한 목록은 아니지만, 대부분의 함수를 다룹니다.”[5]
그런 다음 270개가 넘는 함수를 나열하는데, 그중 통계와 관련된 함수는 극히 일부에 불과합니다.
- Numpy는 고강도 수치 작업을 수행하는 사용자를 대상으로 하므로, 계산 수학 및 컴퓨터 과학에 대한 배경 지식이 없는 사용자에게는 부담스러울 수 있습니다. 예를 들어,
numpy.mean은 네 개의 인자를 받습니다.:mean(a, axis=None, dtype=None, out=None)
다행히 초보자나 일반적인 numpy 사용자에게는 세 가지가 선택 사항이며, 간단한 경우에는
numpy.mean이 올바르게 처리합니다.:>>> numpy.mean([1, 2, 3, 4]) 2.5
- 많은 사람에게 numpy를 설치하는 일은 어렵거나 불가능할 수 있습니다. 예를 들어 기업 환경의 사람들은 서드파티 소프트웨어를 설치하도록 허가받기 전에 어렵고 시간이 많이 걸리는 절차를 거쳐야 할 수 있습니다. 일반적인 Python 사용자에게 숫자 목록의 평균을 구하기 위해 서드파티 패키지 설치 방법을 배워야 한다는 것은 안타까운 일입니다.
이로 인해 두 번째 선택지인 직접 만드는 통계 함수로 이어집니다. 언뜻 보면 일반적인 통계 함수의 겉보기 단순성 때문에 매력적인 선택지로 보입니다. 예를 들어:
def mean(data):
return sum(data)/len(data)
def variance(data):
# Use the Computational Formula for Variance.
n = len(data)
ss = sum(x**2 for x in data) - (sum(data)**2)/n
return ss/(n-1)
def standard_deviation(data):
return math.sqrt(variance(data))
위의 결과는 간단히 테스트해 보면 올바른 것처럼 보입니다.:
>>> data = [1, 2, 4, 5, 8]
>>> variance(data)
7.5
그러나 모든 데이터 요소에 상수를 더해도 분산은 변하지 않아야 합니다.:
>>> data = [x+1e12 for x in data]
>>> variance(data)
0.0
그리고 분산은 절대로 음수가 되어서는 안 됩니다.:
>>> variance(data*100)
-1239429440.1282566
이와 대조적으로, 제안된 참조 구현은 처음 두 예제에서 정확히 올바른 답인 7.5를 얻으며, 세 번째 예제에서도 합리적으로 가까운 답인 6.012를 얻습니다. numpy도 더 낫지는 않습니다 [6].
단순한 통계 계산에도 부주의한 사용자를 위한 함정이 있으며, 그 시작은 계산 공식 자체입니다. 이름과 달리 이 공식은 수치적으로 불안정하며, 위에서 볼 수 있듯이 매우 부정확할 수 있습니다. 이는 컴퓨터를 사용한 계산에는 완전히 부적합합니다 [7]. 코더들이 수치적으로 부정확한 동일한 코드를 계속해서 다시 만들거나 [9], 다른 사람들에게 그렇게 하라고 조언하기 때문에 [10], 이 문제는 Python뿐만 아니라 많은 프로그래밍 언어의 사용자들을 괴롭힙니다 [8].
분산과 표준 편차만의 문제가 아닙니다. 평균조차도 보이는 것만큼 간단하지는 않습니다. 위의 구현은 문제가 생기기에는 너무 단순해 보이지만, 실제로 문제가 발생합니다:
- 내장
sum은 크기가 크게 다른 부동 소수점을 처리할 때 정확도를 잃을 수 있습니다. 따라서 위의 순진한mean은 이 “고문 테스트”를 통과하지 못합니다.:assert mean([1e30, 1, 3, -1e30]) == 1
그 결과 1 대신 0을 반환하며, 순전히 계산상의 오류가 100%입니다.
mean내부에서math.fsum을 사용하면 부동 소수점 데이터에 대해 더 정확해지지만, 불필요한 경우에도 모든 인자를 부동 소수점으로 변환하는 부작용이 있습니다. 예를 들어 Fraction 목록의 평균은 부동 소수점이 아니라 Fraction일 것으로 예상해야 합니다.
위의 평균 구현은 순진한 분산 구현만큼 치명적으로 실패하지는 않지만, 표준 라이브러리 함수는 직접 만든 버전보다 훨씬 더 잘 처리할 수 있습니다.
위의 예제에는 특히 좋지 않은 데이터 집합이 사용되었지만, 더욱 현실적인 데이터 집합에서도 정확도는 중요합니다. 데이터의 변동을 해석할 때 첫 단계는 (조건이 나쁜 데이터를 처리하는 것을 포함하여) 데이터를 분산이 1인 계열(그리고 흔히 평균이 0인 계열)로 표준화하는 것입니다. 이러한 표준화에는 원래 계열의 평균과 분산을 정확하게 계산하는 작업이 필요합니다. 평균과 분산을 순진하게 계산하면 매우 빠르게 정밀도를 잃을 수 있습니다. 정밀도가 정확도를 제한하므로, 실용적으로 사용할 수 있는 평균과 분산 계산 알고리즘 중 가장 정밀한 것을 사용하는 것이 중요합니다. 그렇지 않으면 표준화 결과 자체가 쓸모없게 됩니다.
다른 언어/패키지와의 비교
제안된 통계 라이브러리는 numpy/scipy와 같은 서드파티 라이브러리나, Minitab, SAS 및 Matlab과 같이 전문 통계학자를 대상으로 하는 독점적인 모든 기능을 갖춘 통계 패키지와 경쟁하려는 것이 아닙니다. 이 라이브러리는 그래프 계산기와 공학용 계산기 수준을 대상으로 합니다.
대부분의 프로그래밍 언어는 통계 함수에 대한 기본 제공 지원이 거의 없거나 전혀 없습니다. 몇 가지 예외가 있습니다:
R -
R(그리고 그 독점적 사촌인 S)은 통계 작업을 위해 설계된 프로그래밍 언어입니다. 통계학자들 사이에서 매우 인기가 높으며 기능도 매우 풍부합니다 [11].
C#
C# LINQ 패키지에는 열거 가능한 객체의 평균을 계산하는 확장 메서드가 포함되어 있습니다 [12].
Ruby
Ruby에는 표준 통계 모듈이 함께 제공되지 않지만, 이에 대한 수요가 어느 정도 있는 것으로 보입니다 [13]. Statsample은 R과 경쟁하는 것을 목표로 하는, 기능이 매우 풍부한 서드파티 라이브러리로 보입니다 [14].
PHP
PHP에는 고급 통계 함수가 매우 풍부하게 포함되어 있습니다(대부분 문서화되어 있지는 않습니다) [15].
Delphi
Delphi는 Math 라이브러리에 Mean, Sum, Variance, TotalVariance, MomentSkewKurtosis를 비롯한 표준 통계 함수를 포함합니다 [16].
GNU Scientific Library
GNU Scientific Library에는 표준 통계 함수, 백분위수, 중앙값 및 기타 기능이 포함되어 있습니다 [17]. GSL에서 차용한 한 가지 혁신은 분산과 표준 편차를 계산할 때 호출자가 표본의 미리 계산된 평균(또는 선험적으로 알려진 모집단 평균)을 선택적으로 지정할 수 있도록 하는 것입니다 [18].
모듈의 설계 결정
처음부터 모든 것을 포함하려고 하기보다는 작게 시작하여 필요에 따라 라이브러리를 확장하는 것이 제 의도입니다. 따라서 현재 참조 구현에는 평균, 분산, 표준 편차, 중앙값, 최빈값이라는 소수의 함수만 포함되어 있습니다. (전체 목록은 참조 구현을 확인하십시오.)
다음과 같은 설계 기능을 목표로 했습니다:
- 속도보다 정확성을 우선합니다. 정확하지만 느린 함수를 빠르게 만드는 것이 빠르지만 버그가 있는 함수를 수정하는 것보다 쉽습니다.
- 일회 순회 알고리즘을 위해 정확도를 잠재적으로 희생하기보다는, 데이터를 두 번 순회할 수 있도록 시퀀스의 데이터에 집중합니다. 함수는 데이터가 리스트 또는 다른 시퀀스로 전달될 것으로 예상합니다. 이터레이터가 전달되면 내부적으로 리스트로 변환할 수 있습니다.
- 함수는 가능한 한 모든 유형의 수치 데이터를 처리해야 합니다. 예를 들어, Decimals 리스트의 평균은 float이 아니라 Decimal이어야 합니다. 이것이 불가능한 경우에는 float을 “가장 낮은 공통 데이터 형식”으로 취급합니다.
- 함수는 float, Decimal 또는 Fraction의 데이터 세트를 지원하지만, 혼합된 데이터 세트까지 지원된다는 보장은 없습니다. (반면 명시적으로 거부되지도 않습니다.)
- 기본 개념은 이해하지만 (예를 들어) 어떤 분산을 사용해야 하는지(모집단 분산인지 표본 분산인지?)는 모를 수 있는 독자를 대상으로 충분한 문서를 제공합니다. 수학자와 통계학자는 표기법과 용어 모두에서 일관성이 없는 끔찍한 습관이 있으며 [19], 사용되는 모순되고 혼란스러운 정의를 이해하는 데 많은 시간을 보냈으므로, 이 주제를 모호하게 만들기보다는 명확히 설명하기 위해 최선을 다하는 것이 공정합니다.
- 그러나 지루한 [20] 수학적 세부 사항까지 들어가지는 마십시오.
API
라이브러리의 초기 버전은 일변량(단일 변수) 통계 함수를 제공합니다. 일반 API는 function(data, ...) -> result라는 함수형 모델을 기반으로 하며, 여기서 data는 필수 이터러블인 (대개) 수치 데이터입니다.
작성자는 리스트가 가장 일반적으로 사용되는 데이터 유형일 것으로 예상하지만, 모든 이터러블 유형을 허용해야 합니다. 필요한 경우 함수는 내부적으로 리스트로 변환할 수 있습니다. 가능한 경우 함수는 데이터 값의 유형을 유지해야 합니다. 예를 들어 Decimal 리스트의 평균은 float가 아니라 Decimal이어야 합니다.
평균, 중앙값 및 최빈값 계산
mean, median* 및 mode 함수는 하나의 필수 인자를 받아 적절한 통계값을 반환합니다. 예를 들어 다음과 같습니다.:
>>> mean([1, 2, 3])
2.0
제공되는 함수는 다음과 같습니다.
mean(data)- data의 산술 평균입니다.
median(data)- data의 중앙값(중간값)이며, 값의 개수가 짝수이면 두 중간값의 평균을 취합니다.
median_high(data)- data의 상위 중앙값이며, 항목 수가 짝수이면 두 중간값 중 큰 값을 취합니다.
median_low(data)- data의 하위 중앙값이며, 항목 수가 짝수이면 두 중간값 중 작은 값을 취합니다.
median_grouped(data, interval=1)- 보간을 사용한 그룹화된 data의 50번째 백분위수입니다.
mode(data)- 가장 자주 나타나는 data항목입니다.
mode는 데이터 인자가 수치여야 한다는 규칙의 유일한 예외입니다. 문자열과 같은 명목형 데이터의 이터러블도 허용합니다.
분산 및 표준 편차 계산
과학용 계산기와 유사하게 작동하도록 통계 모듈에는 모집단 및 표본 분산과 표준 편차를 위한 별도의 함수가 포함됩니다. 네 함수 모두 하나의 필수 인자인 수치 데이터의 이터러블을 받는 유사한 시그니처를 가집니다. 예를 들어 다음과 같습니다.:
>>> variance([1, 2, 2, 2, 3])
0.5
네 함수 모두 두 번째 선택적 인자인 데이터의 평균도 받습니다. 이는 GNU Scientific Library [18]가 제공하는 유사한 API를 모델로 합니다. 이 인자를 사용하는 사례는 특별한 순서 없이 다음 세 가지입니다.
- 평균값을 a priori로 알고 있습니다.
- 이미 평균을 계산했으며, 이를 다시 계산하지 않으려고 합니다.
- 평균이 아닌 특정 지점을 기준으로 한 2차 모멘트를 계산하기 위해 분산 함수를 (남용하여) 사용하려고 합니다.
각 경우에 주어진 인자가 의미 있는지 확인하는 것은 호출자의 책임입니다.
제공되는 함수는 다음과 같습니다.
variance(data, xbar=None)- data의 표본 분산이며, 선택적으로 xbar를 표본 평균으로 사용합니다.
stdev(data, xbar=None)- data의 표본 표준 편차이며, 선택적으로 xbar를 표본 평균으로 사용합니다.
pvariance(data, mu=None)- data의 모집단 분산이며, 선택적으로 mu를 모집단 평균으로 사용합니다.
pstdev(data, mu=None)- data의 모집단 표준 편차이며, 선택적으로 mu를 모집단 평균으로 사용합니다.
기타 함수
공개 함수가 하나 더 있습니다.
sum(data, start=0)- 수치 data의 고정밀 합입니다.
사양
제안된 참조 구현은 순수 Python으로 작성되므로 다른 Python 구현에서는 이 모듈을 수정 없이 쉽게 사용하거나, 필요에 맞게 조정할 수 있습니다.
모듈의 이름은 무엇이어야 합니까?
이는 최상위 모듈 statistics가 됩니다.
math를 패키지로 전환하고 이를 math의 하위 모듈로 만들자는 의견도 있었지만, 최종적으로는 일반적인 합의에 따라 최상위 모듈로 결정되었습니다. 그 밖에 고려되었으나 거부된 이름으로는 stats (기존 stat 모듈과 혼동될 위험이 너무 큼)와 statslib (“너무 C와 비슷하다”고 설명됨)가 있습니다.
논의 및 해결된 문제
이 제안은 이전에 여기 [21]에서 논의되었습니다.
Python-Ideas에서의 논의와 초기 코드 검토 과정에서 여러 설계 문제가 해결되었습니다. 표준 라이브러리에 또 다른 sum함수를 추가하는 것에 대해 많은 우려가 있었으며, 자세한 내용은 아래 FAQ를 참조하십시오. 또한 sum의 초기 구현은 Decimal을 처리할 때 일부 반올림 문제와 기타 설계 문제를 겪었습니다. 이를 해결하는 데 Oscar Benjamin의 지원은 매우 귀중했습니다.
또 다른 문제는 이터레이터 형식의 데이터를 처리하는 방법이었습니다. 첫 번째 variance 구현은 데이터가 이터레이터 형식인지 시퀀스 형식인지에 따라 원 패스 알고리즘과 투 패스 알고리즘 사이를 조용히 전환했습니다. 이는 설계상의 실수로 드러났습니다. 사용된 알고리즘에 따라 계산된 분산이 약간 달라질 수 있었기 때문에, variance등은 내부적으로 리스트를 생성하고 항상 더 정확한 투 패스 구현을 사용하도록 변경되었습니다.
논란이 많았던 한 설계는 중앙값을 계산하는 함수와 관련된 것으로, 해당 함수들은 median 호출 가능 객체의 속성으로 구현되었습니다. 예를 들어 median, median.low, median.high 등이 이에 해당합니다. 표준 라이브러리의 unittest.mock에 이러한 스타일의 기존 사용 사례가 하나 이상 있기는 하지만, 코드 검토자들은 이 방식이 표준 라이브러리에는 지나치게 이례적이라고 판단했습니다. 따라서 설계는 유사 네임스페이스 명명 규칙을 사용하는 별도 함수들로 구성된 보다 전통적인 방식으로 변경되었습니다. median_low, median_high 등이 그 예입니다.
코드 검토자들이 우려한 또 다른 문제는 연속형 데이터의 표본 최빈값을 계산하는 함수가 존재한다는 점이었으며, 일부 사람들은 알고리즘의 선택과 이것이 포함할 만큼 충분히 일반적인 요구인지에 의문을 제기했습니다. 따라서 이 함수는 API에서 제외되었으며, 이제 mode는 고유값을 세는 기본적인 교과서식 알고리즘만 구현합니다.
또 다른 중요한 논의 사항은 timedelta 객체의 통계량을 계산하는 것이었습니다. statistics 모듈이 timedelta 객체를 직접 지원하지는 않지만, 먼저 timedelta.total_seconds 메서드를 사용하여 이를 숫자로 변환하면 이 사용 사례를 지원할 수 있습니다.
자주 묻는 질문
이 모듈은 표준 라이브러리로 고려되기 전에 PyPI에서 충분히 사용 기간을 거쳐야 하지 않습니까?
이 모듈의 이전 버전은 2010년부터 PyPI [22]에서 사용할 수 있었습니다. numpy보다 훨씬 단순하므로 수년간의 외부 개발이 필요하지 않습니다.
표준 라이브러리에 정말 sum의 또 다른 버전이 필요합니까?
이것은 참조 구현에서 가장 논란이 많았던 부분으로 드러났습니다. 한 가지 관점에서는 분명히 합계가 세 개나 되는 것은 두 개가 너무 많습니다. 그러나 다른 관점에서는 그렇습니다. 기존의 두 버전이 적합하지 않은 이유는 여기 [23]에 설명되어 있지만, 간단히 요약하면 다음과 같습니다.
- 내장 sum은 부동소수점 수를 사용할 때 정밀도를 잃을 수 있습니다.
- 내장 sum은 문자열과 바이트를 제외하면
+연산자를 지원하는 숫자가 아닌 모든 데이터 형식을 허용합니다. math.fsum은 고정밀도이지만 모든 인자를 부동소수점 수로 강제 변환합니다.
기존 합계 함수 중 하나 또는 다른 하나를 “수정”하는 데 관심을 보인 사람들이 있었습니다. 이것이 3.4 기능 동결 전에 발생한다면 statistics.sum을 유지하기로 한 결정을 재고할 수 있습니다.
이 모듈은 이전 버전의 Python으로 백포팅됩니까?
이 모듈은 현재 3.3을 대상으로 하며, 가까운 장래에는 3.3용으로 PyPI에 공개할 예정입니다. 3.x 계열의 이전 버전으로 백포팅하는 것은 가능성이 높습니다(아직 결정되지는 않았습니다). 2.7로 백포팅할 가능성은 더 낮지만 배제되지는 않았습니다.
이것은 numpy를 대체하기 위한 것입니까?
아닙니다. 수년에 걸쳐 기능이 늘어날 가능성은 높지만(아래의 미해결 이슈 참조), numpy를 대체하거나 직접 경쟁하는 것을 목표로 하지는 않습니다. Numpy는 전문가를 대상으로 하는 모든 기능을 갖춘 수치 라이브러리이며, Python 생태계의 수치 라이브러리에서 핵 반응로와 같은 존재입니다. 이는 “배터리가 포함된”이라는 의미의 그저 배터리일 뿐이며, “numpy 사용”과 “직접 자체 버전 만들기” 사이 어딘가의 중간 수준을 목표로 합니다.
향후 작업
- 현재로서는 선형 회귀, 상관 계수, 공분산과 같은 다변량 통계 함수에 가장 적합한 API가 무엇인지 확신하지 못하고 있습니다. 가능한 API는 다음과 같습니다.
- x 및 y 데이터에 대한 별도의 인자:
function([x0, x1, ...], [y0, y1, ...])
- (x, y) 데이터에 대한 하나의 인자:
function([(x0, y0), (x1, y1), ...])
이 API는 GvR [24]이 선호합니다.
- 2차원 배열에서 임의의 열 선택:
function([[a0, x0, y0, z0], [a1, x1, y1, z1], ...], x=1, y=2)
- 위의 내용 중 일부를 조합한 것입니다.
다변량 통계에 선호되는 API에 대한 합의가 없으므로, 이러한 다변량 함수의 포함은 Python 3.5까지 연기합니다.
- x 및 y 데이터에 대한 별도의 인자:
- 마찬가지로, 확률 변수의 확률을 계산하는 함수와 추론 검정 함수(예: Student’s t-test)의 포함도 3.5까지 연기합니다.
- 데이터를 리스트로 변환하지 않고 이터레이터 형식의 데이터에서 여러 통계량을 계산할 수 있는 단일 패스 함수를 포함하는 데 상당한 관심이 있습니다. PyPI의 실험적
stats패키지에는 통계 함수의 코루틴 버전이 포함되어 있습니다. 이러한 기능의 포함은 3.5까지 연기합니다.
참고 자료
Copyright
This document has been placed in the public domain.