miniforge: 파이썬과 R의 패키지 및 개발 환경 관리 도구

파이썬과 R의 의존성 지옥

파이썬과 R에는 유용한 라이브러리들이 아주 많습니다. 그러다보니 여러 라이브러리들이 버전이 서로 충돌하거나 호환되지 않는 경우가 발생하곤 합니다. 예를 들어 A 라이브러리가 B 라이브러리 1.0 버전에 기반해 만들어졌는데 C 라이브러리가 B 라이브러리 2.0 버전을 요구한다면 A와 C를 동시에 사용할 수 없는 경우가 생깁니다.

이런 의존성 문제를 해결하기 위해 다양한 방법론들이 등장했습니다. 파이썬의 경우에는 venv + pip, Pyenv, Poetry, Conda, PDM 등과 같은 새로운 라이브러리를 이용해 프로젝트 별로 의존성을 관리할 수 있도록 합니다. 문제는 이런 도구들도 만능이 아니기에 의존성 지옥 탈출은 요원해 보입니다.

콘다(conda)

콘다는 Windows, macOS 및 Linux에서 실행되는 오픈 소스 패키지 관리 시스템이자 환경 관리 시스템으로 파이썬 뿐만 아니라 R 패키지도 지원합니다. 그래서 주로 과학 분야에서 많이 사용되는 도구입니다. 그러나 덩치가 커서 작은 프로젝트에는 너무 많은 의존성과 리소스를 잡아먹는다는 단점이 있죠.

miniforge

miniforge는 덩치큰 아나콘다를 아주 날씬하게 만드는 프로젝트입니다. 그래서 아래와 같은 특징을 갖습니다.

  • 기본(그리고 유일한) 채널로 conda-forge를 사용.
  • 표준 Python 인터프리터(일명 "CPython") 대신 PyPy에 대한 지원.
  • 콘다보다 더 빠른 맘바(mamba)도 지원.
  • 다양한 CPU 아키텍처(x86_64, ppc64le, Apple M1을 포함한 aarch64)지원.

맘바(mamba)는 또 뭐죠?

콘다의 다른 단점에 라이브러리 설치 속도가 느리다는 점이 었습니다. 그래서 C++으로 작성된 맘바라는 도구가 새로 나오게 되었죠.

파이썬 생태계는 이런 것이 특징입니다. 항상 새로운 도구가 우후죽순 나옵니다.

맘바포지(mamba-forge)

맘바포지는 제가 생각하는 현재 시점에서 패키지와 개발 환경 관리 문제를 가장 쉽게 해결하는 도구입니다. 맘바포지를 설치하는 방법과 개발 환경을 설정을 알아보도록 하죠.

설치하기

공식문서에서 각각의 OS에 맞는 설치 방법을 찾아 볼 수 있습니다. 저는 리눅스를 사용하기에 아래 명령어로 설치 하였습니다.

wget "https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-$(uname)-$(uname -m).sh"
bash Mambaforge-$(uname)-$(uname -m).sh

개발 환경 설정

가상환경 만들기

맘바포지의 (base) 환경에 왠만하면 아무런 패키지를 설치하지 않을 것을 권장합니다. 따라서 새로운 가상환경인 ipynb를 만들어 주피터랩을 사용해 보겠습니다.

mamba create -n ipynb python=3.11 r-base r-essentials jupyterlab

콘다를 써본적 있으시다면 바로 아시겠지만 condamamba로 바꾸기만 하면 됩니다. 결과는 아래와 같습니다.

                  __    __    __    __
                 /  \  /  \  /  \  /  \
                /    \/    \/    \/    \
███████████████/  /██/  /██/  /██/  /████████████████████████
              /  / \   / \   / \   / \  \____
             /  /   \_/   \_/   \_/   \    o \__,
            / _/                       \_____/  `
            |/
        ███╗   ███╗ █████╗ ███╗   ███╗██████╗  █████╗
        ████╗ ████║██╔══██╗████╗ ████║██╔══██╗██╔══██╗
        ██╔████╔██║███████║██╔████╔██║██████╔╝███████║
        ██║╚██╔╝██║██╔══██║██║╚██╔╝██║██╔══██╗██╔══██║
        ██║ ╚═╝ ██║██║  ██║██║ ╚═╝ ██║██████╔╝██║  ██║
        ╚═╝     ╚═╝╚═╝  ╚═╝╚═╝     ╚═╝╚═════╝ ╚═╝  ╚═╝

        mamba (1.1.0) supported by @QuantStack

        GitHub:  https://github.com/mamba-org/mamba
        Twitter: https://twitter.com/QuantStack

█████████████████████████████████████████████████████████████


Looking for: ['python=3.11', 'r-base', 'r-essentials', 'jupyterlab']
conda-forge/noarch                                 @   3.7MB/s  3.2s
conda-forge/linux-64                              30.2MB @   4.2MB/s  7.3s


  All requested packages already installed

Preparing transaction: done
Verifying transaction: done
Executing transaction: done

To activate this environment, use

     $ mamba activate ipynb

To deactivate an active environment, use

     $ mamba deactivate

가상환경 활성화 하기

mamba activate ipynb

파이썬 라이브러리 설치하기

아래 명령어를 통해 쉽게 설치할 수 있습니다.

mamba install pandas

R 라이브러리 설치하기

R의 경우 보통 라이브러리 이름 앞에 r-을 붙이면 됩니다만, 확실하지 않기 때문에 다음 search 명령어를 통해 미리 확인하고 설치하면 좋습니다.

mamba search r-tidyverse

위 명령어를 통해 해당 라이브러리가 존재한다는 것을 알 수 있습니다.

mamba install r-tidyverse -y

라이브러리 제거하기

설치한 라이브러리를 제거하고 싶다면 아래 명령어를 사용합니다.

mamba remove r-tidyverse

의존성 파일로 저장하기

개발을 하다보면 배포 혹은 프로젝트간의 전환을 위해 의존성을 파일로 저장해야하는 경우가 생깁니다. 그럴 때에는 아래와 같이 하면 됩니다.

mamba env export > env.yaml
의존성 파일로 부터 가상환경 만들기

위 명령어로 생성된 파일을 가지고 다음 명령어를 사용해 새로운 가상환경을 만들 수 있습니다.

mamba env create -f env.yaml

가상환경 비활성화하기

일반적으로는 그냥 터미널을 꺼버리고는 합니다만, 실수를 방지하기 위해 다음 명령어를 습관적으로 써주는 것이 좋습니다.

mamba deactivate 

끝으로,

파이썬과 R은 다양한 라이브러리를 사용해 프로젝트를 진행합니다. 그렇기에 라이브러리의 버전과 호환성을 관리하는 작업은 쉽지 않습니다. 여러 의존성 관리 도구들이 있지만 각각 장단점이 있고 개발자의 취향과 프로젝트의 요구사항에 따라 어떤 도구를 사용할지가 달라집니다. 파이썬과 R 라이브러리 관리에는 왕도가 없습니다. 최신 도구가 나오면 사용해보고 배우는 것을 멈추지 마세요.

파이썬으로 하는 통계 추론과 유의성 검정

0. 통계적 추론과 유의성 검정

여기에 사용한 코드는 모두 https://github.com/gedeck/practical-statistics-for-data-scientists 에서 가져왔습니다.

(c) 2019 Peter C. Bruce, Andrew Bruce, Peter Gedeck

통계적 추론이라는 것은 제한된 실험 데이터에서 얻은 결과를 모집단에도 적용하려는 것입니다. 이번 포스트에서는 통계적 추론에 사용되는 검정법을 배워볼 예정입니다.

In [1]:
%matplotlib inline

from pathlib import Path
import random

import pandas as pd
import numpy as np

from scipy import stats
import statsmodels.api as sm
import statsmodels.formula.api as smf

import matplotlib.pylab as plt
In [2]:
try:
    import common

    DATA = common.dataDirectory()
except ImportError:
    DATA = Path().resolve() / "data"
In [3]:
WEB_PAGE_DATA_CSV = DATA / "web_page_data.csv"
FOUR_SESSIONS_CSV = DATA / "four_sessions.csv"
CLICK_RATE_CSV = DATA / "click_rates.csv"
IMANISHI_CSV = DATA / "imanishi_data.csv"

1. A/B 검정

A/B 검정은 두 가지 중에 어떤 것이 더 좋은지를 증명하기 위해 실험군을 나누어 진행하는 실험입니다. 일반적으로 대조군(Negative control)과 처리군으로 나눕니다.

예시로 웹페이지에서 고객들이 얼마나 머무는지 측정(session_time)하고 A/B 검정을 해보겠습니다.

In [4]:
session_times = pd.read_csv(WEB_PAGE_DATA_CSV)
session_times.Time = 100 * session_times.Time

먼저 상자 그림 시각화를 통해 비교해보자

In [5]:
ax = session_times.boxplot(by="Page", column="Time", figsize=(4, 4))
ax.set_xlabel("")
ax.set_ylabel("Time (in seconds)")
plt.suptitle("")

plt.tight_layout()
plt.show()
No description has been provided for this image

그림으로 봐서는 페이지B의 방문객들이 좀 더 오래 머무는 것 같다. 각각의 평균값을 구해 수치로 비교해보자.

In [6]:
mean_a = session_times[session_times.Page == "Page A"].Time.mean()
mean_b = session_times[session_times.Page == "Page B"].Time.mean()
print(mean_b - mean_a)
35.66666666666667

대략적으로 36초정도 차이가 난다. 문제는 이 차이가 우연에 의한 것인지 아니면 통계적으로 중요한 것인지 판단하는 것이다.

1.1. 순열 검정을 통한 A/B 검정

순열검정(permutaion test): 두 개 이상의 표본을 함께 결합하여 관측값들을 무작위로 재표본으로 추출하는 과정을 말한다.

파이썬에서 순열 검정을 구현하기 위해 아래와 같이 perm_fun 함수를 정의합니다.

In [7]:
# Permutation test example with stickiness
def perm_fun(x, nA, nB):
    n = nA + nB
    idx_B = set(random.sample(range(n), nB))
    idx_A = set(range(n)) - idx_B
    return x.loc[idx_B].mean() - x.loc[idx_A].mean()


nA = session_times[session_times.Page == "Page A"].shape[0]
nB = session_times[session_times.Page == "Page B"].shape[0]
print(perm_fun(session_times.Time, nA, nB))
24.23809523809524

단 한번의 계산을 통해서 24초라는 차이가 발생하였습니다. 계산을 반복해서 페이지A와 페이지B 사이의 시간 차이에 대한 도수 분포표를 그려봅시다.

In [8]:
random.seed(1)
perm_diffs = [perm_fun(session_times.Time, nA, nB) for _ in range(1000)]

fig, ax = plt.subplots(figsize=(5, 5))
ax.hist(perm_diffs, bins=11, rwidth=0.9)
ax.axvline(x=mean_b - mean_a, color="black", lw=2)
ax.text(50, 190, "Observed\ndifference", bbox={"facecolor": "white"})
ax.set_xlabel("Session time differences (in seconds)")
ax.set_ylabel("Frequency")

plt.tight_layout()
plt.show()
No description has been provided for this image

위 그림에서 수직선은 관측된 차이입니다. 이것을 통해 순열 검정에서 가끔 실제 관찰된 차이를 넘어가는 것을 알 수 있습니다. 그렇다면 어느정도의 확률로 그런 일이 벌어질까요?

In [9]:
print(np.mean(perm_diffs > mean_b - mean_a))
0.121

답은 12.1% 입니다. 이것을 통해 페이지A와 페이지B의 차이인 36초가 통계적으로 봤을때는 차이가 없어도 약 12% 확률로 발생할 수 있다는 결론을 얻었습니다.

1.1.2. t-Test를 사용한 A/B 검정

t-테스트(t-test) 또는 t-검정 또는 스튜던트 t-테스트(Student's t-test)는 검정통계량이 귀무가설 하에서 t-분포를 따르는 통계적 가설 검정법이다. t-테스트는 일반적으로 검정통계량이 정규 분포를 따르며 분포와 관련된 스케일링 변숫값들이 알려진 경우에 사용한다. 이때 모집단의 분산과 같은 스케일링 항을 알 수 없으나, 이를 데이터를 기반으로 한 추정값으로 대체하면 검정통계량은 t-분포를 따른다. 예를 들어 t-테스트를 사용하여 두 데이터 세트(집단)의 평균이 서로 유의하게 다른지 여부를 판별할 수 있다. -wiki

파이썬에서는 ttest_ind 함수를 사용하면 손쉽게 구해볼 수 있습니다.

In [13]:
res = stats.ttest_ind(
    session_times[session_times.Page == "Page A"].Time,
    session_times[session_times.Page == "Page B"].Time,
    equal_var=False,
)
print(f"p-value for single sided test: {res.pvalue / 2:.4f}")
p-value for single sided test: 0.1408

결과 값이 앞서 구한 순열 검정의 확률인 12%과 유사한 수치인 14%임을 확인 할 수 있습니다. 컴퓨터가 보급되기 전에 순열 검정은 실용적이지 않았고 그래서 통계학자들에게 t-Test가 널리 사용되었습니다.

In [14]:
tstat, pvalue, df = sm.stats.ttest_ind(
    session_times[session_times.Page == "Page A"].Time,
    session_times[session_times.Page == "Page B"].Time,
    usevar="unequal",
    alternative="smaller",
)
print(f"p-value: {pvalue:.4f}")
p-value: 0.1408

2. 통계적 유의성과 P-Values

통계적 유의성이란 실험 결과가 우연하게 일어난 것인지 아닌지 판단하는 것입니다. 유의 확률(p-value)는 통계적 유의성을 판단하기 위해 가정한 귀무가설에서 극단적인 결과를 얻을 확률입니다. 따라서 p-value가 낮을수록 우연한 일이 아니라고 생각 할 수 있습니다.

다음의 예시 데이터를 가지고 예를 들어보도록 하겠습니다. 동일한 내용이지만 구성이 다른 상품판매 페이지A, 페이지B가 있고 방문자가 구매한 횟수와 그렇지 않은 데이터를 모았다고 생각합시다.

결과 페이지A 페이지B
구매 200 182
구매하지 않음 23539 22406

페이지A가 페이지B보다 약 5% 정도 우수한 결과를 보여주었는데 이것이 통계적으로 유의성을 갖는지 순열 검정을 통해 살펴봅시다.

In [10]:
random.seed(1)
obs_pct_diff = 100 * (200 / 23739 - 182 / 22588)
print(f"Observed difference: {obs_pct_diff:.4f}%")
conversion = [0] * 45945
conversion.extend([1] * 382)
conversion = pd.Series(conversion)

perm_diffs = [100 * perm_fun(conversion, 23739, 22588) for _ in range(1000)]

fig, ax = plt.subplots(figsize=(5, 5))
ax.hist(perm_diffs, bins=11, rwidth=0.9)
ax.axvline(x=obs_pct_diff, color="black", lw=2)
ax.text(0.06, 200, "Observed\ndifference", bbox={"facecolor": "white"})
ax.set_xlabel("Conversion rate (percent)")
ax.set_ylabel("Frequency")

plt.tight_layout()
plt.show()
Observed difference: 0.0368%
No description has been provided for this image

약 1000번의 순열 검정을 통해 얻은 히스토그램을 살펴보니 관측된 값은 충분히 우연에 의한 것으로 보입니다. 그러나 좀 더 명확하게 하기 위해 p-value를 계산해봅시다.

2.1. P-Value 구하기

p-value는 순열 검정에서 얻은 결과 중에 관찰된 차이와 같거나 더 큰 차이를 보이는 경우의 비율이라고 할 수 있기에 다음과 같이 추정할 수 있습니다.

In [11]:
print(np.mean([diff > obs_pct_diff for diff in perm_diffs]))
0.332

예상한 것처럼 30%의 확률로 우연에 의해서 나타날 수 있는 차이였습니다. 따라서 페이지A와 페이지B의 차이는 통계적으로 유의미하지 않다고 말할 수 있겠습니다.

3. 일원 분산분석(ANOVA )

A/B 검정이 아닌 여러 그룹간의 통계적 유의미한 차이를 검정하는 절차를 분산분석이라 합니다. 예시로 페이지 1,2,3,4에 방문자들이 머문 시간에 대한 데이터를 살펴보겠습니다. 먼저 데이터셋을 불러옵니다.

In [16]:
print(pd.read_csv(FOUR_SESSIONS_CSV).head())
     Page  Time
0  Page 1   164
1  Page 2   178
2  Page 3   175
3  Page 4   155
4  Page 1   172

네 그룹의 상자그림을 시각화해서 어떤 차이가 있는지 살펴봅시다.

In [15]:
four_sessions = pd.read_csv(FOUR_SESSIONS_CSV)

ax = four_sessions.boxplot(by="Page", column="Time", figsize=(4, 4))
ax.set_xlabel("Page")
ax.set_ylabel("Time (in seconds)")
plt.suptitle("")
plt.title("")

plt.tight_layout()
plt.show()
No description has been provided for this image

3.1. 순열검정을 통한 one-way ANOVA

파이썬에서는 다음 코드를 사용해 순열 검정을 통해 ANOVA 분석을 진행할 수 있습니다.

In [17]:
observed_variance = four_sessions.groupby("Page").mean().var()[0]
print("Observed means:", four_sessions.groupby("Page").mean().values.ravel())
print("Variance:", observed_variance)


# Permutation test example with stickiness
def perm_test(df):
    df = df.copy()
    df["Time"] = np.random.permutation(df["Time"].values)
    return df.groupby("Page").mean().var()[0]


print(perm_test(four_sessions))
Observed means: [172.8 182.6 175.6 164.6]
Variance: 55.426666666666655
57.02666666666678
In [18]:
random.seed(1)
perm_variance = [perm_test(four_sessions) for _ in range(3000)]
print("Pr(Prob)", np.mean([var > observed_variance for var in perm_variance]))

fig, ax = plt.subplots(figsize=(5, 5))
ax.hist(perm_variance, bins=11, rwidth=0.9)
ax.axvline(x=observed_variance, color="black", lw=2)
ax.text(60, 200, "Observed\nvariance", bbox={"facecolor": "white"})
ax.set_xlabel("Variance")
ax.set_ylabel("Frequency")

plt.tight_layout()
plt.show()
Pr(Prob) 0.07766666666666666
No description has been provided for this image

Pr(Prob)의 값은 p-value이며 결과는 0.07입니다. 통상적인 임계 p-value 값인 0.05이상임으로 네 페이지간의 차이가 우연히 발생할 수 있다고 결론 내릴 수 있습니다.

3.2. F-통계량을 통한 one-way ANOVA

F-통계량은 잔차 오류에 인한 분산과 그룹 평균의 분산에 대한 비율을 기초로 합니다. 비율이 높으면 통계적으로 유의미 하다고 할 수 있고 이를 토대로 p-value를 계산할 수 있습니다.

In [19]:
model = smf.ols("Time ~ Page", data=four_sessions).fit()

aov_table = sm.stats.anova_lm(model)
print(aov_table)
            df  sum_sq     mean_sq         F    PR(>F)
Page       3.0   831.4  277.133333  2.739825  0.077586
Residual  16.0  1618.4  101.150000       NaN       NaN

df는 자유도, sum_sq는 제곱합, mean_sq는 평균제곱, F는 F-통계량을 나타냅니다.

In [20]:
res = stats.f_oneway(
    four_sessions[four_sessions.Page == "Page 1"].Time,
    four_sessions[four_sessions.Page == "Page 2"].Time,
    four_sessions[four_sessions.Page == "Page 3"].Time,
    four_sessions[four_sessions.Page == "Page 4"].Time,
)
print(f"F-Statistic: {res.statistic / 2:.4f}")
print(f"p-value: {res.pvalue / 2:.4f}")
F-Statistic: 1.3699
p-value: 0.0388

F-통계량을 사용한 방법은 p-value 값이 더 적게나와 임계값인 0.05 이하입니다. 그러나 ANOVA 분석의 p-value가 낮게 나왔다고 해서 모든 그룹에서 통계적으로 차이가 있다고 할 수는 없습니다. 추가적인 Ad hoc 분석을 진행해 어떤 그룹에서 차이가 있는지 확인해보아야 합니다.

4. 마무리하며

현재 실험 데이터를 분석할때는 통계적 추론과 유의성 검증은 필수적인 절차입니다. 그렇기 때문에 반대로 실험에 대조군을 반드시 포함하며 데이터에 인위적인 조작이 없도록 하는 것이 더욱 중요해 졌습니다.