스포츠 경기분석과 자료의 시각화¶

스포츠경기분석가 아카데미(기초과정)

데이터 2025 K리그1 34R — FC서울 4 : 2 강원FC


목차¶

파트 내용
0 준비 — 폰트 · pitch.py · 데이터
1 경기장 그리기 (pitch.py 기본)
2 슈팅맵
3 패스맵
4 히트맵
5 종합 한 장 (히트맵+패스+슛)
6 이 라이브러리(ksme_visuals)로 할 수 있는 것 — 자유도 총정리

준비 — 폰트 · 라이브러리 · 데이터¶

아래 세 셀을 순서대로 실행

In [6]:
#@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
Out[6]:
'NanumGothic'
In [7]:
import ksme_visuals
print(ksme_visuals.__version__)
0.3.0

pitch.py 올리기 (경기장 그리는 라이브러리)¶

In [8]:
#@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¶

In [9]:
xls="/content/match_events.xlsx"
In [10]:
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 건
Out[10]:
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), 원점=왼쪽 아래.

all_pitches.png

matplotlib 기본 코드¶

plt.figure(figsize=(8, 5)) # Figure만 만들고¶

plt.plot([1,2,3], [4,5,6]) # 현재 figure에 바로 그림¶

plt.show()¶

1️⃣ 경기장 그리기¶

pitch.py 의 Pitch 가 배경(코트) 만 그리기 그 위에 점·선·히트맵은 우리가 matplotlib 으로 얹기. (종목에 따라 — sport=종목이름 바꾸기)

pitch = Pitch(sport="soccer")     # 종목 이름
fig, ax = pitch.draw()            # fig=그림판, ax=그릴 면
In [11]:
from ksme_visuals import Pitch    # 설치한 라이브러리에서 불러오기

pitch = Pitch(sport="soccer")             # 기본 잔디색
fig, ax = pitch.draw(figsize=(9, 6))
plt.show()
No description has been provided for this image

색 바꾸기¶

pitch_color(바닥), line_color(선)

In [12]:
pitch = Pitch(sport="soccer", pitch_color="#16243a", line_color="#dfe7ef", linewidth=2)
fig, ax = pitch.draw(figsize=(9, 6))
plt.show()
No description has been provided for this image

2️⃣ 점 찍기 — 슈팅맵 한 줄씩 만들기¶

한 번에 완성하지 않고, 코드를 한 줄씩 추가하며 결과가 어떻게 바뀌는지 확인 좌표계: x=길이(0-105m), y=폭(0-68m). 그래서 ax.scatter(x, y) 하면 제자리에 찍힘

STEP 1. 그릴 데이터 고르기 — 서울의 슈팅만 (필터링!)

In [13]:
seoul_shots = events[(events["team"]=="서울") & (events["event"]=="슈팅")]
print("서울 슛:", len(seoul_shots), "개")
seoul_shots[["minute", "player", "x", "y", "outcome", "xg"]]
서울 슛: 20 개
Out[13]:
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. 빈 경기장부터 — 배경 먼저 띄우기

In [14]:
pitch = Pitch(sport="soccer", pitch_color="#16243a", line_color="#dfe7ef")
fig, ax = pitch.draw(figsize=(9, 6))
plt.show()
No description has been provided for this image

STEP 3. 점 찍기 — ax.scatter(x, y)

In [15]:
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()
No description has been provided for this image

STEP 4. 점 꾸미기 — 크기(s)·색(color)·테두리(edgecolors)

In [16]:
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()
No description has been provided for this image

STEP 5. 결과를 색으로 구분 — 골/유효/빗나감을 다른 색으로 표현. 딕셔너리로 "결과→색"을 짝지어, 각 점에 맞는 색상 부여

In [17]:
# 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()
No description has been provided for this image

STEP 6. 찬스의 질을 크기로 — 점 크기 = 기대득점(xG). 좋은 찬스일수록 점이 크게 표현되게(s 에 숫자 대신 열 정보)

In [18]:
# 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()
No description has been provided for this image

STEP 7. 제목과 배경색 — 제목과 배경색 더하기! zorder(점을 맨 위로), alpha(살짝 투명)도 추가

In [19]:
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()
No description has been provided for this image

STEP 8. 마무리 — 선수, 시간 주석

In [20]:
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()
No description has been provided for this image

화살표 — 패스맵¶

패스: 시작점(x,y) → 끝점(end_x,end_y)

STEP 1. 데이터 고르기 — 서울의 성공한 패스 필터링

In [21]:
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 개
Out[21]:
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=...) 이 화살표 하나

image.png

In [22]:
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
No description has been provided for this image

STEP 3. 반복문으로 전부 — 패스가 많으니 for 으로 한번에 (끝점이 비어있는 패스는 if 로 건너뛰기)

In [23]:
# 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()
No description has been provided for this image

STEP 4. 정돈하기 — 화살표가 너무 진하면 지저분해 보일 수 있음

alpha(투명도)를 낮추고, 시작점에 작은 점을 찍고, 제목·배경 추가

In [24]:
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()
No description has been provided for this image

직접 해보기 — 린가드의 패스만¶

멀티골(2골)을 넣은 린가드의 패스만 그려보세요.

In [25]:
seoul_pass
Out[25]:
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

In [26]:
# 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()
No description has been provided for this image

4️⃣ 히트맵¶

"이 팀이 주로 어디서 활동했나?"

STEP 1. 데이터 고르기 — 서울의 패스

In [27]:
print("서울 패스 이벤트:", len(seoul_pass), "개")
서울 패스 이벤트: 568 개

STEP 2. ① 칸별로 세기 — 경기장을 격자(30×20칸)로 나눠 각 칸에 점이 몇 개 들어갔는지 셉니다. np.histogram2d 가 해줍니다.

image.png

In [28]:
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가 작을 수록 원본을 유지)

image.png

image.png

In [29]:
from scipy.ndimage import gaussian_filter
H = gaussian_filter(H, sigma=3.0)
print("이제 H 값이 부드럽게 번졌습니다. (값은 소수가 됨)")
이제 H 값이 부드럽게 번졌습니다. (값은 소수가 됨)

STEP 4. ③ 경기장 위에 깔기 — ax.imshow 로 히트맵 얹기 origin="lower" = 원점을 왼쪽 아래로 맞춤(경기장과 정렬), cmap = 색 테마.

heatmap_HT_comparison.png

In [30]:
# 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
No description has been provided for this image

바꿔보기 — cmap을 "hot", "viridis", "inferno", "YlOrRd" 로, sigma 조절해보기

In [31]:
# 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()
No description has been provided for this image

고해상도로 저장하기 (발표용)¶

fig.savefig("my_shotmap.png", dpi=200, bbox_inches="tight",
            facecolor=fig.get_facecolor())   # 배경색까지 저장

6️⃣ 종합 한 장 — 히트맵 + 패스 + 슛¶

배운 걸 모두 한 그림에 겹쳐 한장으로 완성

종합 — 히트맵 + 패스 + 슛 한 장에 겹치기¶

In [32]:
# 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()
No description has been provided for this image

이 라이브러리(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() 로 확인하세요.

In [33]:
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 경기장으로 (라인은 이미지에 있으니 배경만)¶

In [34]:
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축 뒤집기
No description has been provided for this image

예시 B — 종목만 바꾸기 (농구 코트에 점 3개)¶

좌표계는 종목마다 자동(농구=28×15). Pitch.length, Pitch.width 로 크기 조회 가능

In [35]:
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
No description has been provided for this image

예시 C — 다른 프로그램 데이터를 다른 종목에 얹기 (핵심!)¶

data_bounds + transform 은 종목이 바뀌어도 똑같이 동작 종목 크기(L·W)는 pitch가 아니까, 내 데이터 라인 모서리만 알면 됨

In [36]:
# 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" 등으로 바꿔도 그대로 동작
No description has been provided for this image
In [36]: