준비 — 폰트 · 라이브러리 · 데이터¶
아래 세 셀을 순서대로 실행
#@title 맨 처음 한 번만 실행 — 라이브러리 설치 + 한글폰트 { display-mode: "form" }
# 그래프에서 한글이 네모(□)로 깨지지 않게 폰트를 설정.
# Colab은 새로 켤 때마다 초기화되므로, 세션을 새로 시작하면 다시 실행.
!pip install -q ksme_visuals==0.3.0
from ksme_visuals import set_korean_font
set_korean_font(font="NanumGothic")
✅ 한글 폰트 설정 완료: NanumGothic
'NanumGothic'
import ksme_visuals
print(ksme_visuals.__version__)
0.3.0
pitch.py 올리기 (경기장 그리는 라이브러리)¶
#@title 🏟️ 경기장 그리기 준비 { display-mode: "form" }
# 위에서 이미 ksme_visuals 를 설치했으니, 여기선 Pitch 만 불러오면 됨.
from ksme_visuals import Pitch
print("✅ 준비 완료! 사용 가능한 종목:", len(Pitch.available()), "개")
print(" 예시:", Pitch.available()[:10], "...")
✅ 준비 완료! 사용 가능한 종목: 47 개 예시: ['soccer', 'futsal', 'basketball', 'handball', 'volleyball', 'tennis', 'badminton', 'tabletennis', 'field_hockey', 'rugby'] ...
데이터 올리기 — match_events.xlsx¶
xls="/content/match_events.xlsx"
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
events = pd.read_excel(xls, sheet_name="events")
players = pd.read_excel(xls, sheet_name="players")
print("이벤트:", len(events), "건")
events.head()
이벤트: 1609 건
| event_id | minute | period | team | player | back_no | event | outcome | code | x | y | end_x | end_y | pass_length | key_pass | assist | xg | xpass | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 0 | 1 | 강원 | 이상헌 | 22 | 패스 | 성공 | PSS | 52.5 | 33.5 | 22.6 | 27.4 | 30.5 | N | N | NaN | 0.317 |
| 1 | 2 | 0 | 1 | 강원 | 박청효 | 21 | 패스 | 실패 | PSU | 27.1 | 26.2 | 69.2 | 55.7 | 51.5 | N | N | NaN | 0.965 |
| 2 | 3 | 0 | 1 | 서울 | 최준 | 16 | 클리어링 | 성공 | CLG | 69.0 | 55.4 | NaN | NaN | NaN | N | N | NaN | NaN |
| 3 | 4 | 0 | 1 | 강원 | 홍철 | 33 | 패스 | - | PS | 75.4 | 68.0 | 57.9 | 61.0 | 18.9 | N | N | NaN | 0.415 |
| 4 | 5 | 0 | 1 | 강원 | 홍철 | 33 | 패스 | 성공 | PSS | 75.4 | 68.0 | 57.9 | 61.0 | 18.9 | N | N | NaN | 0.425 |
좌표계: x=길이(0 - 105m), y=폭(0 - 68m), 원점=왼쪽 아래.
1️⃣ 경기장 그리기¶
pitch.py 의 Pitch 가 배경(코트) 만 그리기
그 위에 점·선·히트맵은 우리가 matplotlib 으로 얹기.
(종목에 따라 — sport=종목이름 바꾸기)
pitch = Pitch(sport="soccer") # 종목 이름
fig, ax = pitch.draw() # fig=그림판, ax=그릴 면
from ksme_visuals import Pitch # 설치한 라이브러리에서 불러오기
pitch = Pitch(sport="soccer") # 기본 잔디색
fig, ax = pitch.draw(figsize=(9, 6))
plt.show()
색 바꾸기¶
pitch_color(바닥), line_color(선)
pitch = Pitch(sport="soccer", pitch_color="#16243a", line_color="#dfe7ef", linewidth=2)
fig, ax = pitch.draw(figsize=(9, 6))
plt.show()
2️⃣ 점 찍기 — 슈팅맵 한 줄씩 만들기¶
한 번에 완성하지 않고, 코드를 한 줄씩 추가하며 결과가 어떻게 바뀌는지 확인
좌표계: x=길이(0-105m), y=폭(0-68m). 그래서 ax.scatter(x, y) 하면 제자리에 찍힘
STEP 1. 그릴 데이터 고르기 — 서울의 슈팅만 (필터링!)
seoul_shots = events[(events["team"]=="서울") & (events["event"]=="슈팅")]
print("서울 슛:", len(seoul_shots), "개")
seoul_shots[["minute", "player", "x", "y", "outcome", "xg"]]
서울 슛: 20 개
| minute | player | x | y | outcome | xg | |
|---|---|---|---|---|---|---|
| 69 | 4 | 둑스 | 9.0 | 32.0 | 유효 | 0.068 |
| 321 | 21 | 이승모 | 3.5 | 40.3 | 유효 | 0.195 |
| 421 | 25 | 안데르손 | 17.0 | 30.4 | 막힘 | 0.067 |
| 522 | 35 | 이승모 | 22.2 | 40.6 | 막힘 | 0.039 |
| 554 | 38 | 김진수 | 25.8 | 29.2 | 빗나감 | 0.025 |
| 763 | 50 | 이승모 | 27.4 | 27.0 | 유효 | 0.017 |
| 804 | 45 | 조영욱 | 85.6 | 38.6 | 빗나감 | 0.075 |
| 974 | 58 | 야잔 | 97.3 | 44.3 | 유효 | 0.104 |
| 1027 | 61 | 둑스 | 99.5 | 36.3 | 유효 | 0.223 |
| 1094 | 66 | 안데르손 | 91.9 | 45.1 | 막힘 | 0.051 |
| 1107 | 66 | 야잔 | 87.5 | 22.0 | 유효 | 0.038 |
| 1149 | 70 | 조영욱 | 99.6 | 29.2 | 빗나감 | 0.306 |
| 1173 | 72 | 린가드 | 97.9 | 33.6 | 골 | 0.263 |
| 1218 | 74 | 조영욱 | 100.0 | 45.8 | 유효 | 0.089 |
| 1252 | 77 | 린가드 | 84.4 | 57.3 | 골 | 0.047 |
| 1266 | 79 | 류재문 | 84.4 | 27.7 | 골 | 0.038 |
| 1331 | 85 | 린가드 | 88.1 | 50.9 | 빗나감 | 0.020 |
| 1377 | 89 | 문선민 | 91.7 | 27.3 | 빗나감 | 0.252 |
| 1508 | 97 | 문선민 | 97.6 | 38.1 | 빗나감 | 0.380 |
| 1541 | 99 | 천성훈 | 93.8 | 32.4 | 골 | 0.299 |
STEP 2. 빈 경기장부터 — 배경 먼저 띄우기
pitch = Pitch(sport="soccer", pitch_color="#16243a", line_color="#dfe7ef")
fig, ax = pitch.draw(figsize=(9, 6))
plt.show()
STEP 3. 점 찍기 — ax.scatter(x, y)
pitch = Pitch(sport="soccer", pitch_color="#16243a", line_color="#dfe7ef")
fig, ax = pitch.draw(figsize=(9, 6))
ax.scatter(seoul_shots["x"], seoul_shots["y"]) # ← 이 줄이 새로 추가됨
plt.show()
STEP 4. 점 꾸미기 — 크기(s)·색(color)·테두리(edgecolors)
pitch = Pitch(sport="soccer", pitch_color="#16243a", line_color="#dfe7ef")
fig, ax = pitch.draw(figsize=(9, 6))
ax.scatter(seoul_shots["x"], seoul_shots["y"],
s=140, color="#ffd166", edgecolors="black") # ← 옵션 추가
plt.show()
STEP 5. 결과를 색으로 구분 — 골/유효/빗나감을 다른 색으로 표현. 딕셔너리로 "결과→색"을 짝지어, 각 점에 맞는 색상 부여
# 1) cmap 딕셔너리: 결과→색 (골/유효/빗나감/막힘)
# 2) colors = outcome 열 .map(cmap) 후 .fillna("#6c757d")
# 3) Pitch 그리고 ax.scatter 에 c=colors
cmap = {"골":"#ffd166", "유효":"#ef476f", "빗나감":"#8d99ae", "막힘":"#6c757d"}
colors = seoul_shots["outcome"].map(cmap).fillna("#6c757d")
pitch = Pitch(sport="soccer", pitch_color="#16243a", line_color="#dfe7ef")
fig, ax = pitch.draw(figsize=(9, 6))
ax.scatter(seoul_shots["x"], seoul_shots["y"],
s=140, c=colors, edgecolors="black") # color → c=colors 로
plt.show()
STEP 6. 찬스의 질을 크기로 — 점 크기 = 기대득점(xG).
좋은 찬스일수록 점이 크게 표현되게(s 에 숫자 대신 열 정보)
# 1) sizes = xg 열 .fillna(0.05) * 1000 + 60
# 2) ax.scatter 의 s 에 sizes 넣기 (c=colors 유지)
sizes = seoul_shots["xg"].fillna(0.05) * 1000 + 60
pitch = Pitch(sport="soccer", pitch_color="#16243a", line_color="#dfe7ef")
fig, ax = pitch.draw(figsize=(9, 6))
ax.scatter(seoul_shots["x"], seoul_shots["y"],
s=sizes, c=colors, edgecolors="black") # s 에 sizes
plt.show()
STEP 7. 제목과 배경색 — 제목과 배경색 더하기!
zorder(점을 맨 위로), alpha(살짝 투명)도 추가
pitch = Pitch(sport="soccer", pitch_color="#16243a", line_color="#dfe7ef")
fig, ax = pitch.draw(figsize=(9, 6))
fig.set_facecolor("#16243a") # 그림 바깥 배경
ax.scatter(seoul_shots["x"], seoul_shots["y"],
s=sizes, c=colors, edgecolors="black", zorder=3, alpha=.95)
ax.set_title("FC서울 슛맵 (색=결과, 크기=기대득점 xG)", color="white", fontsize=14)
plt.show()
STEP 8. 마무리 — 선수, 시간 주석
pitch = Pitch(sport="soccer", pitch_color="#16243a", line_color="#dfe7ef")
fig, ax = pitch.draw(figsize=(9, 6))
fig.set_facecolor("#16243a") # 그림 바깥 배경
ax.scatter(seoul_shots["x"], seoul_shots["y"],
s=sizes, c=colors, edgecolors="black", zorder=3, alpha=.95)
ax.set_title("FC서울 슛맵 (색=결과, 크기=기대득점 xG)", color="white", fontsize=14)
for _, r in seoul_shots.iterrows():
if r["outcome"]=="골":
label = r["player"] + " " + str(r["minute"]) + "분"
ax.text(r["x"]+1, r["y"]+1.5, label,
color="#dfe7ef", fontsize=10, zorder=4)
plt.show()
화살표 — 패스맵¶
패스: 시작점(x,y) → 끝점(end_x,end_y)
STEP 1. 데이터 고르기 — 서울의 성공한 패스 필터링
seoul_pass = events[(events["team"]=="서울") & (events["event"]=="패스")]
mask = seoul_pass["period"] == 2
seoul_pass.loc[mask, "x"] = 105 - seoul_pass.loc[mask, "x"]
seoul_pass.loc[mask, "y"] = 68 - seoul_pass.loc[mask, "y"]
seoul_pass.loc[mask, "end_x"] = 105 - seoul_pass.loc[mask, "end_x"]
seoul_pass.loc[mask, "end_y"] = 68 - seoul_pass.loc[mask, "end_y"]
seoul_succ_pass=seoul_pass[seoul_pass["outcome"]=="성공"]
print("서울 성공 패스:", len(seoul_succ_pass), "개")
seoul_succ_pass[["minute", "player", "x", "y", "end_x", "end_y"]].head()
서울 성공 패스: 429 개
| minute | player | x | y | end_x | end_y | |
|---|---|---|---|---|---|---|
| 25 | 1 | 김진수 | 72.4 | 0.8 | 63.6 | 1.6 |
| 55 | 2 | 강현무 | 95.2 | 20.9 | 88.0 | 8.6 |
| 56 | 2 | 김진수 | 86.5 | 10.1 | 97.2 | 28.8 |
| 57 | 3 | 강현무 | 97.0 | 30.8 | 91.9 | 57.9 |
| 58 | 3 | 박성훈 | 85.1 | 60.7 | 78.3 | 66.8 |
STEP 2. 화살표 하나 그려보기 — 첫 번째 패스만.
ax.annotate("", xy=끝점, xytext=시작점, arrowprops=...) 이 화살표 하나
one = seoul_succ_pass.iloc[0] # 첫 번째 패스 한 개
print("시작:", one["x"], one["y"], "→ 끝:", one["end_x"], one["end_y"])
pitch = Pitch(sport="soccer", pitch_color="#0d1b2a", line_color="#dfe7ef")
fig, ax = pitch.draw(figsize=(10, 6.5))
ax.annotate("", xy=(one["end_x"], one["end_y"]), xytext=(one["x"], one["y"]),
arrowprops=dict(arrowstyle="->", color="#ffd166", lw=2))
plt.show()
시작: 72.4 0.8 → 끝: 63.6 1.6
STEP 3. 반복문으로 전부 — 패스가 많으니 for 으로 한번에
(끝점이 비어있는 패스는 if 로 건너뛰기)
# 1) for _, r in seoul_pass.iterrows()
# 2) end_x 결측이면 if pd.notna 로 건너뛰기
# 3) 행마다 ax.annotate 화살표 (xy=끝점, xytext=시작점)
pitch = Pitch(sport="soccer", pitch_color="#0d1b2a", line_color="#dfe7ef")
fig, ax = pitch.draw(figsize=(10, 6.5))
for _, r in seoul_succ_pass.iterrows():
if pd.notna(r["end_x"]):
ax.annotate("", xy=(r["end_x"], r["end_y"]), xytext=(r["x"], r["y"]),
arrowprops=dict(arrowstyle="->", color="#ffd166", lw=1.2))
plt.show()
STEP 4. 정돈하기 — 화살표가 너무 진하면 지저분해 보일 수 있음
alpha(투명도)를 낮추고, 시작점에 작은 점을 찍고, 제목·배경 추가
pitch = Pitch(sport="soccer", pitch_color="#0d1b2a", line_color="#dfe7ef")
fig, ax = pitch.draw(figsize=(10, 6.5)); fig.set_facecolor("#0d1b2a")
ax.scatter(seoul_succ_pass["x"], seoul_succ_pass["y"], s=18, color="#ffd166", zorder=3) # 시작점
for _, r in seoul_succ_pass.iterrows():
if pd.notna(r["end_x"]):
ax.annotate("", xy=(r["end_x"], r["end_y"]), xytext=(r["x"], r["y"]),
arrowprops=dict(arrowstyle="->", color="#ffd166",
alpha=.30, lw=1.0), zorder=2) # alpha 낮춤
ax.set_title("FC서울 성공 패스맵", color="white", fontsize=15)
plt.show()
직접 해보기 — 린가드의 패스만¶
멀티골(2골)을 넣은 린가드의 패스만 그려보세요.
seoul_pass
| event_id | minute | period | team | player | back_no | event | outcome | code | x | y | end_x | end_y | pass_length | key_pass | assist | xg | xpass | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 25 | 26 | 1 | 1 | 서울 | 김진수 | 22 | 패스 | 성공 | PSS | 72.4 | 0.8 | 63.6 | 1.6 | 8.9 | N | N | NaN | 0.928 |
| 32 | 33 | 1 | 1 | 서울 | 야잔 | 5 | 패스 | 실패 | PSU | 84.6 | 26.7 | 74.7 | 68.0 | 42.5 | N | N | NaN | 0.154 |
| 55 | 56 | 2 | 1 | 서울 | 강현무 | 31 | 패스 | 성공 | PSS | 95.2 | 20.9 | 88.0 | 8.6 | 14.3 | N | N | NaN | 0.912 |
| 56 | 57 | 2 | 1 | 서울 | 김진수 | 22 | 패스 | 성공 | PSS | 86.5 | 10.1 | 97.2 | 28.8 | 21.5 | N | N | NaN | 0.984 |
| 57 | 58 | 3 | 1 | 서울 | 강현무 | 31 | 패스 | 성공 | PSS | 97.0 | 30.8 | 91.9 | 57.9 | 27.6 | N | N | NaN | 0.860 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1597 | 1598 | 103 | 2 | 서울 | 린가드 | 10 | 패스 | 성공 | PSS | 12.2 | 67.2 | 13.2 | 61.4 | 5.8 | N | N | NaN | 0.856 |
| 1598 | 1599 | 103 | 2 | 서울 | 안데르손 | 70 | 패스 | 성공 | PSS | 14.1 | 60.5 | 14.7 | 57.3 | 3.3 | N | N | NaN | 0.763 |
| 1599 | 1600 | 103 | 2 | 서울 | 황도윤 | 41 | 패스 | 성공 | PSS | 11.6 | 55.6 | 6.3 | 55.4 | 5.2 | N | N | NaN | 0.916 |
| 1603 | 1604 | 103 | 2 | 서울 | 류재문 | 29 | 패스 | 성공 | PSS | 25.2 | 37.1 | 20.6 | 15.1 | 22.4 | N | N | NaN | 0.980 |
| 1604 | 1605 | 103 | 2 | 서울 | 김진수 | 22 | 패스 | 성공 | PSS | 20.6 | 15.1 | 10.0 | 18.0 | 11.0 | N | N | NaN | 0.860 |
568 rows × 18 columns
# 1) rng_pass 필터: player=="린가드", event=="패스"
# 2) for 로 행마다 ax.annotate 화살표 (xy=(end_x,end_y), xytext=(x,y))
# 3) 성공이면 초록(#06d6a0) 아니면 빨강(#ef476f)
# 4) 시작점에 ax.scatter 작은 점
rng_pass = seoul_pass[(seoul_pass["player"]=="린가드") & (seoul_pass["event"]=="패스")]
pitch = Pitch(sport="soccer", pitch_color="#0d1b2a", line_color="#dfe7ef")
fig, ax = pitch.draw(figsize=(10, 6.5)); fig.set_facecolor("#0d1b2a")
for _, r in rng_pass.iterrows():
if pd.notna(r["end_x"]):
col = "#06d6a0" if r["outcome"]=="성공" else "#ef476f"
ax.annotate("", xy=(r["end_x"], r["end_y"]), xytext=(r["x"], r["y"]),
arrowprops=dict(arrowstyle="->", color=col, alpha=.8, lw=1.6), zorder=2)
ax.scatter(r["x"], r["y"], s=28, color=col, zorder=3)
ax.set_title("린가드 패스맵 (초록=성공 · 빨강=실패)", color="white", fontsize=14)
plt.show()
4️⃣ 히트맵¶
"이 팀이 주로 어디서 활동했나?"
STEP 1. 데이터 고르기 — 서울의 패스
print("서울 패스 이벤트:", len(seoul_pass), "개")
서울 패스 이벤트: 568 개
STEP 2. ① 칸별로 세기 — 경기장을 격자(30×20칸)로 나눠
각 칸에 점이 몇 개 들어갔는지 셉니다. np.histogram2d 가 해줍니다.
import numpy as np
H, xe, ye = np.histogram2d(seoul_pass["x"], seoul_pass["y"],
bins=(30, 20), range=[[0,105],[0,68]])
print("H 모양:", H.shape, " (30칸 x 20칸의 '개수 표')")
print("가장 많은 칸의 개수:", int(H.max()))
H 모양: (30, 20) (30칸 x 20칸의 '개수 표') 가장 많은 칸의 개수: 8
STEP 3. ② 부드럽게 표현 — 주변 값들을 평균내어 데이터를 부드럽게 만드는(노이즈를 제거하는) 필터
gaussian_filter 사용.
sigma : 얼마나 부드럽게 표현할 것인가를 결정하는 가중치(sigma가 클수록 더 넓은 범위를 평균 냄 -> 스무딩. sigma가 작을 수록 원본을 유지)
from scipy.ndimage import gaussian_filter
H = gaussian_filter(H, sigma=3.0)
print("이제 H 값이 부드럽게 번졌습니다. (값은 소수가 됨)")
이제 H 값이 부드럽게 번졌습니다. (값은 소수가 됨)
STEP 4. ③ 경기장 위에 깔기 — ax.imshow 로 히트맵 얹기
origin="lower" = 원점을 왼쪽 아래로 맞춤(경기장과 정렬), cmap = 색 테마.
# 1) Pitch 그리기
# 2) ax.imshow(H.T, ...) 로 히트맵 얹기
# 3) cmap="magma", origin="lower", extent=[0,105,0,68]
pitch = Pitch(sport="soccer", pitch_color="#0d1b2a", line_color="white")
fig, ax = pitch.draw(figsize=(10, 6.5)); fig.set_facecolor("#0d1b2a")
print(pitch.length, pitch.width)
ax.imshow(H.T, extent=[0,105,0,68], origin="lower",
cmap="magma", alpha=.8, interpolation="bilinear", zorder=1.1, aspect="auto")
ax.set_title("FC서울 활동 히트맵", color="white", fontsize=15)
plt.show()
# ①세기 → ②번지기 → ③깔기, 3단계로 히트맵 완성!
105.0 68.0
바꿔보기 —
cmap을"hot","viridis","inferno","YlOrRd"로,sigma조절해보기
# 1) rng_pass-> 린가드 패스
# 2) np.histogram2d 로 칸별 세기
# 3) gaussian_filter(sigma=1.6) 로 번지기
# 4) ax.imshow cmap="viridis" 로 깔기
pitch = Pitch(sport="soccer", pitch_color="#0d1b2a", line_color="white")
fig, ax = pitch.draw(figsize=(10, 6.5)); fig.set_facecolor("#0d1b2a")
H,_,_ = np.histogram2d(rng_pass["x"], rng_pass["y"], bins=(30,20), range=[[0,105],[0,68]])
H = gaussian_filter(H, sigma=3.0)
ax.imshow(H.T, extent=[0,105,0,68], origin="lower",
cmap="viridis", alpha=.8, interpolation="bilinear", zorder=1.1, aspect="auto")
ax.set_title("린가드 활동 히트맵", color="white", fontsize=15)
plt.show()
고해상도로 저장하기 (발표용)¶
fig.savefig("my_shotmap.png", dpi=200, bbox_inches="tight",
facecolor=fig.get_facecolor()) # 배경색까지 저장
6️⃣ 종합 한 장 — 히트맵 + 패스 + 슛¶
배운 걸 모두 한 그림에 겹쳐 한장으로 완성
종합 — 히트맵 + 패스 + 슛 한 장에 겹치기¶
# 1) t = events 에서 team=="서울" 필터
# 2) (1) np.histogram2d + gaussian_filter 후 ax.imshow 히트맵 옅게
# 3) (2) 성공 패스만 골라 for 로 ax.annotate 화살표 옅게
# 4) (3) 슛(event=="슈팅")만 골라 ax.scatter, s=xg*1000+60
from scipy.ndimage import gaussian_filter
t = seoul_pass
pitch = Pitch(sport="soccer", pitch_color="#0d1b2a", line_color="white")
fig, ax = pitch.draw(figsize=(10, 6.5)); fig.set_facecolor("#0d1b2a")
H,_,_ = np.histogram2d(t["x"], t["y"], bins=(30,20), range=[[0,105],[0,68]])
H = gaussian_filter(H, 3.0)
ax.imshow(H.T, extent=[0,105,0,68], origin="lower", cmap="viridis",
alpha=.5, interpolation="bilinear", zorder=1.05, aspect="auto")
tp = t[(t["event"]=="패스") & (t["outcome"]=="성공")]
for _, r in tp.iterrows():
if pd.notna(r["end_x"]):
ax.annotate("", xy=(r["end_x"],r["end_y"]), xytext=(r["x"],r["y"]),
arrowprops=dict(arrowstyle="->", color="white", alpha=.15, lw=.8), zorder=2)
sh = t[t["event"]=="슈팅"]
ax.scatter(sh["x"], sh["y"], s=sh["xg"].fillna(.05)*1000+60,
color="yellow", edgecolors="black", zorder=4)
ax.set_title(f"FC서울 활동 히트맵 + 패스 + 슛", color="white", fontsize=16)
plt.show()
이 라이브러리(ksme_visuals)로 할 수 있는 것 — 자유도 총정리¶
| 하고 싶은 것 | 어떻게 |
|---|---|
| 종목 바꾸기 | Pitch(sport="basketball") — 46개 종목 지원 |
| 색·테마 | pitch_color, line_color, THEME 딕셔너리 |
| 세로로 | Pitch(sport="soccer", vertical=True) |
| 라인 밖 데이터 | pad=(5,5) + ax.scatter(..., clip_on=False) |
| 다른 프로그램 데이터 | data_bounds=(...), origin=, pitch.transform(x,y) |
| 크기(픽셀) | pitch.draw(px=(1200,800), dpi=100) |
| 컬링 반쪽 | Pitch(sport="curling", half=True) |
| 고해상도 저장 | fig.savefig("out.png", dpi=200, facecolor=fig.get_facecolor()) |
지원 종목 전체 목록은
Pitch.available()로 확인하세요.
print("지원 종목:", Pitch.available())
지원 종목: ['soccer', 'futsal', 'basketball', 'handball', 'volleyball', 'tennis', 'badminton', 'tabletennis', 'field_hockey', 'rugby', 'american_football', 'baseball', 'softball', 'cricket', 'netball', 'lacrosse', 'beach_volleyball', 'squash', 'racquetball', 'padel', 'pickleball', 'ice_hockey', 'curling', 'short_track', 'speed_skating', 'water_polo', 'swimming_pool', 'athletics_track', 'velodrome', 'archery', 'shooting', 'bowling', 'boccia', 'judo', 'taekwondo', 'wrestling', 'karate', 'fencing', 'boxing', 'weightlifting', 'gymnastics_floor', 'equestrian', 'goalball', 'sitting_volleyball', 'wheelchair_rugby', 'blind_football', 'custom']
예시 A: 이미지 전체를 custom 경기장으로 (라인은 이미지에 있으니 배경만)¶
from ksme_visuals import Pitch
pitch = Pitch(sport="custom", length=2500, width=2500, background="/content/stadium.png")
fig, ax = pitch.draw()
#ax.scatter(x, 88 - y) # 수집한 데이터의 원점이 좌상단일 경우 y축 뒤집기
예시 B — 종목만 바꾸기 (농구 코트에 점 3개)¶
좌표계는 종목마다 자동(농구=28×15). Pitch.length, Pitch.width 로 크기 조회 가능
pitch = Pitch(sport="basketball", pitch_color="#1d3557", line_color="white")
print("이 코트 크기:", pitch.length, "x", pitch.width, "m") # pitch가 알려줌
fig, ax = pitch.draw(figsize=(9, 5)); fig.set_facecolor("#1d3557")
ax.scatter([24, 25, 5], [7, 11, 4], s=200, color="#ffd166", edgecolors="black", zorder=3)
ax.set_title("농구 코트 예시", color="white", fontsize=14)
plt.show()
이 코트 크기: 28.0 x 15.0 m
예시 C — 다른 프로그램 데이터를 다른 종목에 얹기 (핵심!)¶
data_bounds + transform 은 종목이 바뀌어도 똑같이 동작
종목 크기(L·W)는 pitch가 아니까, 내 데이터 라인 모서리만 알면 됨
# 1) Pitch(sport="농구/배구 등", data_bounds=(x_min,x_max,y_min,y_max), origin=...)
# 2) fig, ax = pitch.draw()
# 3) ax.scatter(*pitch.transform(내x, 내y)) — 원본 좌표 그대로
# (예시) 배구 데이터가 0~100 스케일, 좌상단 원점이라면
import numpy as np
demo_x = np.array([0, 100, 50, 0, 100]) # 내 데이터라고 가정
demo_y = np.array([0, 0, 50, 100, 100])
pitch = Pitch(sport="volleyball", pitch_color="#0d1b2a", line_color="white",
data_bounds=(0, 300, 0, 300), origin="top-left") #botton-left
fig, ax = pitch.draw(figsize=(9, 5)); fig.set_facecolor("#0d1b2a")
ax.scatter(*pitch.transform(demo_x, demo_y),
s=150, c="#ffd166", edgecolors="black", zorder=5)
ax.set_title("배구 코트 + 내 데이터(좌표 자동 변환)", color="white", fontsize=13)
plt.show()
# ▸ sport 만 "basketball", "handball" 등으로 바꿔도 그대로 동작