diff --git a/README.md b/README.md
index c84d574..c7ae92f 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ Matheus Facure의 [Causal Inference for The Brave and True](https://matheusfacur
@@ -48,6 +46,8 @@ book:
page-footer:
left: "[가짜연구소](https://pseudo-lab.com/) 인과추론팀 옮김 · 원서 © [Matheus Facure](https://matheusfacure.github.io/python-causality-handbook/landing-page.html)"
center:
+ - text: 인과추론 허브
+ href: https://causalinferencelab.github.io/
- text: 번역 가이드
href: https://github.com/CausalInferenceLab/Causal-Inference-with-Python/blob/main/TRANSLATION_GUIDE.md
- text: 오류 제보
@@ -115,13 +115,16 @@ format:
html-math-method: mathjax
df-print: default
link-external-newwindow: true
+ link-external-filter: '^(?:https?:)\/\/causalinferencelab\.github\.io\/'
back-to-top-navigation: true
include-after-body:
- sidebar-resize.html
- chapter-toc.html
- site-search.html
+ - reader.html
- comments.html
include-in-header:
+ - analytics.html
- text: |
diff --git a/book/analytics.html b/book/analytics.html
new file mode 100644
index 0000000..b9ed928
--- /dev/null
+++ b/book/analytics.html
@@ -0,0 +1,19 @@
+
+
+
diff --git a/book/index.qmd b/book/index.qmd
index 4bd4c2f..d2f9526 100644
--- a/book/index.qmd
+++ b/book/index.qmd
@@ -7,17 +7,22 @@ aliases:
```{=html}
인과추론팀 Causal-Lab
Python으로 하는
인과추론
Causal Inference for The Brave and True 한국어판. 인과추론의 기초부터 실무 응용까지, 모든 실습을 파이썬으로.
+Causal Inference for The Brave and True 한국어판. 인과추론의 기초부터 실무 응용까지, 모든 실습을 파이썬으로.
+
+
+
+```
+
+이 책은 가짜연구소 인과추론팀의 [Causal Inference Hub](https://causalinferencelab.github.io/)에 있는 자료입니다. 허브는 팀이 2022년부터 만든 오픈소스와 자료를 L0부터 L4까지 커리큘럼으로 엮어 둔 곳이고, 이 책은 L1('어떻게 추정하나')의 중심 자료입니다. 허브에서는 이 책을 'Brave & True 인과추론 번역서'로 소개합니다.
+
+인과추론이 처음이라면 L0의 [영상으로 여는 인과추론](https://causalinferencelab.github.io/causal-studio/)으로 먼저 감을 잡고 넘어와도 좋습니다. 책을 마친 뒤에는 관심에 따라 이렇게 이어 갈 수 있습니다.
+
+- 내 데이터에 적용해 보고 싶다면: [산업 사례 템플릿](https://causalinferencelab.github.io/#station=causal-inference-practice), [책 예제 코드](https://causalinferencelab.github.io/#station=causal-inference-in-python-code)
+- 메타러너와 이중 머신러닝을 코드로 더 익히고 싶다면: ML 트랙의 [CausalML 스터디](https://causalinferencelab.github.io/#station=causal-inference-with-causalML)
+- A/B 테스트를 제대로 하고 싶다면: 실험 트랙의 [온라인 통제 실험](https://causalinferencelab.github.io/#station=OCE-Materials)
+- 헬스케어와 규제 연구가 궁금하다면: 연구 트랙의 [RWE 규제 가이드라인](https://causalinferencelab.github.io/#station=Bridging-Causal-Inference-and-Real-World-Evidence-A-Study-of-FDA-and-Other-Regulatory-Guidelines)
+
+각 장 끝에도 그 장 다음에 보면 좋은 허브 자료를 하나씩 붙여 두었습니다. 전체 흐름은 [허브에서 이 책의 자리](https://causalinferencelab.github.io/#station=Causal-Inference-with-Python)를 열어 보면 앞뒤 자료와 함께 한눈에 보입니다.
+
## 이렇게 읽어 주세요
-각 장은 원서의 주피터 노트북을 그대로 옮긴 것이라 코드와 출력이 원서와 같습니다. 노트북 파일은 [GitHub 저장소](https://github.com/CausalInferenceLab/Causal-Inference-with-Python/tree/main/causal-inference-for-the-brave-and-true)에서 내려받아 직접 실행해 볼 수 있습니다. 그래프를 그리는 코드처럼 길어서 접어 둔 셀은 **코드**를 눌러 펼치면 됩니다.
+각 장은 원서의 주피터 노트북을 그대로 옮긴 것이라 코드와 출력이 원서와 같습니다. 노트북 파일은 [GitHub 저장소](https://github.com/CausalInferenceLab/Causal-Inference-with-Python/tree/main/causal-inference-for-the-brave-and-true)에서 내려받아 직접 실행해 볼 수 있습니다. 그래프를 그리는 코드처럼 길어서 접어 둔 셀은 **코드**를 눌러 펼치면 됩니다. 끝까지 읽은 장은 왼쪽 목차에 ✓ 표시가 붙고, 다음에 오면 첫 화면에서 이어 읽을 수 있습니다. 이 기록은 지금 쓰는 브라우저에만 남습니다.
용어는 같은 저자의 책을 옮긴 『실무로 통하는 인과추론 with 파이썬』(한빛미디어)과 맞췄습니다. 두 책을 함께 보셔도 용어가 헷갈리지 않습니다. 용어의 원어와 그 용어를 다루는 장은 [용어집](glossary.html)에서 찾을 수 있습니다. 왼쪽 위 검색(단축키 `/` 또는 `Ctrl+K`)에 한국어나 영어 용어를 넣어도 됩니다. 띄어쓰기는 달라도 찾아 줍니다. 번역 원칙과 용어표는 [번역 가이드](https://github.com/CausalInferenceLab/Causal-Inference-with-Python/blob/main/TRANSLATION_GUIDE.md)에 정리해 두었습니다.
diff --git a/book/reader.html b/book/reader.html
new file mode 100644
index 0000000..0195a34
--- /dev/null
+++ b/book/reader.html
@@ -0,0 +1,251 @@
+
diff --git a/book/site-search.html b/book/site-search.html
index 0ae3567..01ee75a 100644
--- a/book/site-search.html
+++ b/book/site-search.html
@@ -17,6 +17,14 @@
var input = document.getElementById("cil-search-input");
var list = document.getElementById("cil-search-results");
var data = null, loading = null, items = [], active = -1, timer = 0;
+ var reported = "";
+ // 무엇을 찾는지 GA4 'search' 이벤트로 남긴다(결과를 열 때, 또는 찾다가 닫을 때 한 번)
+ function report(how) {
+ var q = input.value.trim();
+ if (norm(q).length < 2 || q === reported) return;
+ reported = q;
+ try { if (typeof window.gtag === "function") window.gtag("event", "search", { search_term: q, results: items.length, how: how }); } catch (e) {}
+ }
var SUGGEST = ["잠재적 결과", "성향점수", "이중차분법", "도구변수", "평행 추세", "메타러너", "A/B 테스트"];
function norm(s) {
@@ -133,6 +141,7 @@
}
function close() {
if (box.hidden) return;
+ report("close");
box.hidden = true;
document.documentElement.classList.remove("cil-search-open");
input.blur();
@@ -150,14 +159,14 @@
else if (e.key === "ArrowUp") { e.preventDefault(); move(-1); }
else if (e.key === "Enter") {
var target = items[active >= 0 ? active : 0];
- if (target) { e.preventDefault(); window.location.href = target.getAttribute("href"); close(); }
+ if (target) { e.preventDefault(); report("open"); window.location.href = target.getAttribute("href"); close(); }
} else if (e.key === "Escape") { close(); }
});
box.addEventListener("click", function (e) {
if (e.target.closest("[data-close]")) close();
var chip = e.target.closest("[data-q]");
if (chip) { input.value = chip.getAttribute("data-q"); render(input.value); input.focus(); }
- if (e.target.closest(".cil-search-item")) close();
+ if (e.target.closest(".cil-search-item")) { report("open"); close(); }
});
document.addEventListener("keydown", function (e) {
var typing = /^(INPUT|TEXTAREA|SELECT)$/.test(document.activeElement.tagName) || document.activeElement.isContentEditable;
diff --git a/book/styles-dark.scss b/book/styles-dark.scss
index c3b387f..f25b163 100644
--- a/book/styles-dark.scss
+++ b/book/styles-dark.scss
@@ -144,3 +144,88 @@ body {
.ch-chip b {
color: #c9bcf5;
}
+
+.hub-card {
+ border-color: #2b2e40;
+ background: rgba(180, 165, 234, 0.1);
+ color: #eceaff;
+}
+
+.hub-card:hover {
+ background: rgba(180, 165, 234, 0.18);
+ color: #eceaff;
+}
+
+.hub-card .hub-card-arrow {
+ background: #1d1f2c;
+ color: #c9bcf5;
+}
+
+.cil-hub-mobile {
+ background: rgba(180, 165, 234, 0.14);
+ color: #eceaff;
+}
+
+.cil-chapter-meta {
+ color: #9a9db0;
+}
+
+.cil-chapter-meta a {
+ color: #b4a5ea;
+}
+
+.cil-chapter-meta .cil-read-badge,
+#quarto-sidebar .sidebar-link.is-read .menu-text::after {
+ color: #6fcf97;
+}
+
+.cil-end-row {
+ border-top-color: #2b2e40;
+}
+
+.cil-end-vote .q {
+ color: #eceaff;
+}
+
+.cil-end-vote button {
+ border-color: rgba(180, 165, 234, 0.3);
+ color: #e4e5ee;
+}
+
+.cil-end-vote button[aria-pressed="true"] {
+ background: #b4a5ea;
+ border-color: #b4a5ea;
+ color: #14151f;
+}
+
+.cil-end-vote .thanks,
+.cil-end-progress {
+ color: #9a9db0;
+}
+
+.cil-end-progress .bar {
+ background: rgba(180, 165, 234, 0.16);
+}
+
+.hub-next {
+ border-color: #2b2e40;
+ background: linear-gradient(135deg, rgba(180, 165, 234, 0.12) 0%, #14151f 75%);
+}
+
+.hub-next .hub-next-k {
+ color: #c9bcf5;
+}
+
+.hub-next .hub-next-t {
+ color: #eceaff;
+}
+
+.hub-next .hub-next-a a {
+ border-color: rgba(180, 165, 234, 0.35);
+}
+
+.hub-next .hub-next-a a.primary {
+ background: #b4a5ea;
+ border-color: #b4a5ea;
+ color: #14151f;
+}
diff --git a/book/styles.scss b/book/styles.scss
index 7e3a946..70cd32d 100644
--- a/book/styles.scss
+++ b/book/styles.scss
@@ -428,6 +428,472 @@ main:has(.hero) #title-block-header {
text-align: center;
}
+// 사이드바 맨 위: Causal Inference Hub로 가는 카드
+.hub-card {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+ margin: 0.1rem 0 0.9rem;
+ padding: 0.5rem 0.75rem;
+ border-radius: 12px;
+ border: 1px solid rgba($indigo, 0.16);
+ background: $lavender-soft;
+ color: $navy;
+ text-decoration: none;
+ transition: border-color 0.15s ease, background-color 0.15s ease;
+}
+
+.hub-card:hover {
+ border-color: rgba($indigo, 0.45);
+ background: rgba($lavender, 0.24);
+ color: $navy;
+}
+
+.hub-card .hub-card-arrow {
+ display: grid;
+ place-items: center;
+ flex: none;
+ width: 1.7rem;
+ height: 1.7rem;
+ border-radius: 50%;
+ background: #fff;
+ color: $indigo;
+ font-weight: 700;
+ transition: transform 0.15s ease;
+}
+
+.hub-card:hover .hub-card-arrow {
+ transform: translateX(-2px);
+}
+
+.hub-card .hub-card-text {
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ line-height: 1.3;
+}
+
+.hub-card b {
+ font-size: 0.86rem;
+ font-weight: 750;
+}
+
+.hub-card small {
+ font-size: 0.72rem;
+ color: $sidebar-fg;
+}
+
+// 좁은 화면 상단 막대의 허브 바로가기
+.cil-hub-mobile {
+ display: inline-flex;
+ flex: none;
+ align-self: center;
+ align-items: center;
+ line-height: 1.2;
+ gap: 0.3rem;
+ margin: 0 0.15rem 0 0.4rem;
+ padding: 0.22rem 0.6rem 0.22rem 0.45rem;
+ border-radius: 999px;
+ background: $lavender-soft;
+ color: $navy;
+ font-size: 0.8rem;
+ font-weight: 700;
+ text-decoration: none;
+ white-space: nowrap;
+}
+
+.cil-hub-mobile img {
+ width: 16px;
+ height: auto;
+ margin: 0;
+}
+
+@media (min-width: 992px) {
+ .cil-hub-mobile {
+ display: none;
+ }
+}
+
+// 첫 화면 위치 표시: 인과추론 허브 › 서가
+.hero .hero-crumb {
+ display: flex;
+ align-items: center;
+ gap: 0.45rem;
+ margin-bottom: 1.1rem;
+ font-size: 0.82rem;
+ color: rgba(255, 255, 255, 0.6);
+}
+
+.hero .hero-crumb a {
+ color: rgba(255, 255, 255, 0.88);
+ font-weight: 600;
+ text-decoration: none;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.35);
+}
+
+.hero .hero-crumb a:hover {
+ color: #fff;
+ border-bottom-color: #fff;
+}
+
+// 장 첫머리: 분량, 위치, 노트북
+.cil-chapter-meta {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.35rem 0.9rem;
+ margin: 0.55rem 0 0.2rem;
+ font-size: 0.84rem;
+ color: lighten($body-color, 30%);
+}
+
+.cil-chapter-meta > * + *::before {
+ content: "";
+ display: inline-block;
+ width: 3px;
+ height: 3px;
+ margin: 0 0.9rem 0.2em -0.55rem;
+ border-radius: 50%;
+ background: currentColor;
+ opacity: 0.5;
+ vertical-align: middle;
+}
+
+.cil-chapter-meta a {
+ color: $indigo;
+ text-decoration: none;
+ font-weight: 600;
+}
+
+.cil-chapter-meta a:hover {
+ text-decoration: underline;
+}
+
+.cil-chapter-meta .cil-read-badge {
+ color: #2b8a5a;
+ font-weight: 700;
+}
+
+// 읽기 진행 막대
+.cil-progress {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 1040;
+ height: 3px;
+ pointer-events: none;
+}
+
+.cil-progress i {
+ display: block;
+ height: 100%;
+ background: linear-gradient(90deg, $indigo, $lavender);
+ transform: scaleX(0);
+ transform-origin: 0 50%;
+}
+
+// 왼쪽 목차: 읽은 장
+#quarto-sidebar .sidebar-link.is-read .menu-text::after {
+ content: "✓";
+ margin-left: auto;
+ padding-left: 0.4rem;
+ color: #2b8a5a;
+ font-size: 0.8em;
+ font-weight: 700;
+}
+
+// 첫 화면: 이어 읽기와 진도
+.hero .hero-actions a.primary span {
+ font-weight: 500;
+ opacity: 0.8;
+}
+
+.hero .hero-progress {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.4rem 0.7rem;
+ margin: 0.3rem 0 0;
+ font-size: 0.82rem;
+ color: rgba(255, 255, 255, 0.82);
+}
+
+.hero .hero-progress a {
+ color: #fff;
+ font-weight: 600;
+ text-decoration: underline;
+ text-underline-offset: 3px;
+ text-decoration-color: rgba(255, 255, 255, 0.45);
+}
+
+.hero .hero-sub a {
+ color: inherit;
+ text-decoration: underline;
+ text-underline-offset: 3px;
+ text-decoration-color: rgba(255, 255, 255, 0.4);
+}
+
+.hero .hero-sub a:hover {
+ color: #fff;
+ text-decoration-color: #fff;
+}
+
+.hero .hero-progress .bar {
+ flex: none;
+ width: 90px;
+ height: 5px;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.25);
+ overflow: hidden;
+}
+
+.hero .hero-progress .bar i {
+ display: block;
+ height: 100%;
+ background: #fff;
+}
+
+// 장 끝 마무리: 평가와 진도
+.cil-end {
+ margin-top: 2.8rem;
+}
+
+.cil-end-row {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.8rem 1.4rem;
+ padding: 0.9rem 0;
+ border-top: 1px solid rgba($indigo, 0.15);
+}
+
+.cil-end-vote {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.45rem;
+}
+
+.cil-end-vote .q {
+ margin-right: 0.3rem;
+ font-weight: 700;
+ color: $navy;
+}
+
+.cil-end-vote button {
+ padding: 0.32rem 0.85rem;
+ border-radius: 999px;
+ border: 1px solid rgba($indigo, 0.28);
+ background: transparent;
+ color: $body-color;
+ font-size: 0.86rem;
+ font-weight: 600;
+ cursor: pointer;
+}
+
+.cil-end-vote button:hover {
+ border-color: $indigo;
+}
+
+.cil-end-vote button[aria-pressed="true"] {
+ background: $indigo;
+ border-color: $indigo;
+ color: #fff;
+}
+
+.cil-end-vote .thanks {
+ flex-basis: 100%;
+ font-size: 0.86rem;
+ color: lighten($body-color, 25%);
+}
+
+.cil-end-progress {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+ font-size: 0.84rem;
+ color: lighten($body-color, 25%);
+}
+
+.cil-end-progress .bar {
+ width: 110px;
+ height: 6px;
+ border-radius: 999px;
+ background: rgba($indigo, 0.12);
+ overflow: hidden;
+}
+
+.cil-end-progress .bar i {
+ display: block;
+ height: 100%;
+ background: linear-gradient(90deg, $indigo, $lavender);
+ transition: width 0.4s ease;
+}
+
+.cil-end .hub-next {
+ margin-top: 0.6rem;
+}
+
+// 들어가며: 허브 배너
+a.hub-banner {
+ display: flex;
+ align-items: center;
+ gap: 1.1rem;
+ margin: 0.4rem 0 1.4rem;
+ padding: 1.1rem 1.3rem;
+ border-radius: 16px;
+ color: #fff;
+ text-decoration: none;
+ background:
+ radial-gradient(circle at 90% 10%, rgba($lavender, 0.5), transparent 50%),
+ linear-gradient(135deg, $navy 0%, $indigo 70%);
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
+}
+
+a.hub-banner:hover {
+ color: #fff;
+ transform: translateY(-2px);
+ box-shadow: 0 10px 26px rgba($navy, 0.25);
+}
+
+.hub-banner img {
+ flex: none;
+ width: 52px;
+ height: auto;
+ margin: 0;
+}
+
+.hub-banner-text {
+ display: flex;
+ flex-direction: column;
+ gap: 0.15rem;
+ min-width: 0;
+}
+
+.hub-banner-k {
+ font-size: 0.76rem;
+ font-weight: 600;
+ opacity: 0.75;
+}
+
+.hub-banner-t {
+ font-size: 1.25rem;
+ font-weight: 800;
+ letter-spacing: -0.01em;
+}
+
+.hub-banner-d {
+ font-size: 0.88rem;
+ line-height: 1.6;
+ opacity: 0.88;
+}
+
+.hub-banner-go {
+ flex: none;
+ margin-left: auto;
+ padding: 0.45rem 0.95rem;
+ border-radius: 999px;
+ background: #fff;
+ color: $navy;
+ font-size: 0.86rem;
+ font-weight: 700;
+ white-space: nowrap;
+}
+
+.hub-banner-t {
+ line-height: 1.3;
+}
+
+@media (max-width: 640px) {
+ a.hub-banner {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr);
+ align-items: start;
+ gap: 0.8rem 0.9rem;
+ }
+ .hub-banner img {
+ width: 40px;
+ }
+ .hub-banner-go {
+ grid-column: 1 / -1;
+ justify-self: start;
+ margin-left: 0;
+ }
+}
+
+// 장 끝: Causal Inference Hub에서 이어 볼 자료
+.hub-next {
+ margin: 2.6rem 0 0;
+ padding: 1.2rem 1.3rem 1.1rem;
+ border-radius: 14px;
+ border: 1px solid rgba($indigo, 0.18);
+ background: linear-gradient(135deg, $lavender-soft 0%, #fff 70%);
+}
+
+.hub-next p {
+ margin: 0;
+}
+
+.hub-next .hub-next-k {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 0.45rem;
+ font-size: 0.78rem;
+ font-weight: 700;
+ color: $indigo;
+}
+
+.hub-next .hub-next-k img {
+ display: block;
+ flex: none;
+ width: 18px;
+ height: auto;
+ margin: 0;
+}
+
+.hub-next .hub-next-t {
+ margin-top: 0.45rem;
+ font-size: 1.12rem;
+ font-weight: 750;
+ color: $navy;
+}
+
+.hub-next .hub-next-d {
+ margin-top: 0.25rem;
+ font-size: 0.92rem;
+ line-height: 1.65;
+}
+
+.hub-next .hub-next-a {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ margin-top: 0.85rem;
+}
+
+.hub-next .hub-next-a a {
+ display: inline-block;
+ padding: 0.4rem 0.95rem;
+ border-radius: 999px;
+ border: 1px solid rgba($indigo, 0.3);
+ font-size: 0.86rem;
+ font-weight: 650;
+ text-decoration: none;
+}
+
+.hub-next .hub-next-a a.primary {
+ background: $indigo;
+ border-color: $indigo;
+ color: #fff;
+}
+
+.hub-next .hub-next-a a:hover {
+ opacity: 0.88;
+}
+
// 사이드바 상단: 가짜연구소 × 인과추론팀
.brand-lockup {
display: flex;
@@ -751,6 +1217,14 @@ h1.title .chapter-number {
// 모바일 상단 경로: 부 이름은 숨기고 장 제목만 한 줄로
@media (max-width: 991.98px) {
+ .quarto-secondary-nav .container-fluid {
+ align-items: center;
+ }
+ .quarto-secondary-nav .quarto-page-breadcrumbs {
+ flex: 0 1 auto;
+ min-width: 0;
+ overflow: hidden;
+ }
.quarto-secondary-nav .breadcrumb {
flex-wrap: nowrap;
overflow: hidden;