보충: Pandas 기본기능 총정리 (생성·필터·정렬·삭제·병합)
이번 강의에서 배울 것
- DataFrame, Series, Index의 관계를 설명할 수 있다.
- dict와 list of dict, 파일에서 DataFrame을 만들고 읽은 직후 점검할 수 있다.
- 열 선택, 조건 필터, query, loc·iloc을 구분해 쓸 수 있다.
- sort_values, drop, rename, assign으로 표를 정돈할 수 있다.
- concat과 merge의 차이, merge의 how 옵션 네 가지를 설명할 수 있다.
- groupby와 agg로 그룹별 요약표를 만들고 병합 결과를 검증할 수 있다.
학습 경로
이전: 4주차 · 파일 입출력과 Pandas · 다음: 5주차 · NumPy
범위: 보충 자료 pandas_basic_functions_visual_lecture.pdf 1–17쪽. 4주차에서 배운 불러오기·선택·필터를 한 흐름으로 다시 묶고, 정렬·삭제·병합·groupby를 더한다. 6·7주차 실습에서 쓰는 concat, apply, fillna의 바탕이 된다.
단원 도입
| 구간 | 핵심 질문 | 대표 코드 |
|---|---|---|
| 생성 | 표를 어떻게 만들까? | pd.DataFrame, pd.read_csv |
| 필터 | 필요한 행만 어떻게 남길까? | df[조건], df.query() |
| 정렬·삭제 | 표를 어떻게 정돈할까? | sort_values, drop |
| 병합 | 여러 표를 어떻게 연결할까? | merge, concat |
| 요약 | 그룹별로 어떻게 묶어 볼까? | groupby().agg() |
목표: DataFrame을 표처럼 보고, 코드로 조작하는 감각을 만든다.
형광펜 읽는 법: 노랑 핵심 정의·결론 · 빨강 헷갈리기 쉬운 구분·함정 · 파랑 외워야 할 순서·목록
기본 단위: Series · DataFrame · Index
3쪽
| Index | name | dept | score |
|---|---|---|---|
| 0 | Kim | A | 92 |
| 1 | Lee | B | 83 |
| 2 | Park | A | 77 |
| 3 | Choi | C | 88 |
시작 템플릿과 데이터 점검 순서
4쪽
import pandas as pd
df = pd.read_csv("sales.csv")
print(df.shape) # (행 수, 열 수)
print(df.head()) # 앞 5행
df.info() # 자료형과 결측치| 메서드 | 의미 | 확인할 것 |
|---|---|---|
df.shape | 크기 | 행 수가 예상과 같은가 |
df.head() | 앞부분 | 열 이름과 값이 정상인가 |
df.info() | 구조 | 자료형과 null 여부 |
df.describe() | 요약통계 | 이상치와 범위 |
생성 ① dict와 list of dict
5쪽
data = {
"name": ["Kim", "Lee", "Park"],
"dept": ["A", "B", "A"],
"sales": [120, 95, 180],
}
df = pd.DataFrame(data)
# name dept sales
# 0 Kim A 120
# 1 Lee B 95
# 2 Park A 180
rows = [{"name": "Kim", "sales": 120}, {"name": "Lee", "sales": 95}]
df2 = pd.DataFrame(rows) # 한 행 = 딕셔너리 하나| 방식 | 생각하는 단위 | 어울리는 데이터 |
|---|---|---|
| dict of list | 열 중심: “열 이름: 값 목록” | 직접 만드는 작은 예제 |
| list of dict | 행 중심: “한 행 = dict 하나” | JSON, 웹 API, XML 파싱 결과 (6주차) |
생성 ② 파일에서 읽기
6쪽
df_csv = pd.read_csv("sales.csv")
df_xlsx = pd.read_excel("sales.xlsx", sheet_name="2026")
pd.read_csv("sales.csv", encoding="utf-8-sig") # 한글이 깨질 때
pd.read_csv("sales.csv", parse_dates=["date"]) # 날짜 열을 날짜형으로
pd.read_csv("sales.csv", usecols=["id", "sales"]) # 필요한 열만| 함수 | 입력 | 주요 옵션 |
|---|---|---|
read_csv | csv, txt | encoding, sep, parse_dates, na_values(7주차) |
read_excel | xlsx | sheet_name, usecols |
read_json | json | orient, lines |
read_sql | DB | 쿼리, 연결 객체 |
선택: 열과 행을 정확히 집기
7쪽
| idx | name | dept | sales | cost |
|---|---|---|---|---|
| 0 | Kim | A | 120 | 70 |
| 1 | Lee | B | 95 | 60 |
| 2 | Park | A | 180 | 100 |
| 3 | Choi | C | 88 | 52 |
df["sales"] # 한 열 → Series
df[["name", "sales"]] # 여러 열 → DataFrame (대괄호 두 겹)
df.loc[0:2, ["name", "sales"]] # 이름 기준 → 행 0, 1, 2 (끝 포함) × 열 2개
df.iloc[0:2, 1:3] # 위치 기준 → 행 0, 1 × 열 1, 2 (끝 미포함)필터 ① 조건식 → 마스크 → 남기기
8쪽
| idx | name | sales | sales >= 100 |
|---|---|---|---|
| 0 | Kim | 120 | True |
| 1 | Lee | 95 | False |
| 2 | Park | 180 | True |
| 3 | Choi | 88 | False |
cond = df["sales"] >= 100 # True/False로 된 Series = 마스크(mask)
df2 = df[cond] # True인 행(0, 2)만 남음
df2 = df[df["sales"] >= 100] # 한 줄로도 가능필터 ② 여러 조건 결합하기
9쪽
df[(df["dept"] == "A") & (df["sales"] >= 100)] # A 부서이면서 매출 100 이상 → Kim, Park
df[df["dept"].isin(["A", "B"])] # A 또는 B 부서 → Kim, Lee, Park
df[df["name"].str.contains("Kim", na=False)] # 이름에 Kim 포함
df[df["sales"].between(88, 120)] # 88 이상 120 이하 → Kim, Lee, Choi
df.query("dept == 'A' and sales >= 100") # query 방식 → Kim, Park| 목적 | 문법 | 주의 |
|---|---|---|
| AND | & | 각 조건을 괄호로 묶기 |
| OR | | | 파이프 기호 |
| NOT | ~ | 조건 앞에 붙임 |
| 목록 포함 | isin([...]) | 여러 값 비교 |
| 범위 | between(a, b) | 양 끝 포함 |
정렬: sort_values와 sort_index
10쪽
df.sort_values("sales") # 오름차순: Choi 88, Lee 95, Kim 120, Park 180
df.sort_values("sales", ascending=False) # 내림차순: Park, Kim, Lee, Choi
df.sort_values(["dept", "sales"], ascending=[True, False]) # 부서 오름차순 → 같은 부서 안에서 매출 내림차순
df.sort_index() # 행 인덱스 순서로 되돌리기삭제·수정: drop, rename, assign
11쪽
df2 = df.drop(columns=["cost"]) # 열 삭제
df2 = df.drop(index=[0, 2]) # 행 삭제 → Lee, Choi
df2 = df.rename(columns={"sales": "revenue"}) # 열 이름 변경
df2 = df.assign(profit=df["sales"] - df["cost"]) # 새 열 → 50, 35, 80, 36| name | sales | cost | profit |
|---|---|---|---|
| Kim | 120 | 70 | 50 |
| Lee | 95 | 60 | 35 |
| Park | 180 | 100 | 80 |
병합 ① concat과 merge의 차이
12쪽
pd.concat([df1, df2], axis=0) # 위아래로 (행이 늘어남)
pd.concat([df1, df2], axis=1) # 좌우로 (열이 늘어남)
pd.merge(left, right, on="id", how="left") # id를 기준으로 연결| concat (위아래) | merge (key = id) | ||
|---|---|---|---|
id 1, 2 + id 3, 4 | → id 1, 2, 3, 4 | id, sales + id, dept | → id, sales, dept |
병합 ② how 옵션: inner · left · right · outer
13쪽
left = pd.DataFrame({"id": [1, 2, 3], "sales": [120, 95, 180]})
right = pd.DataFrame({"id": [2, 3, 4], "grade": ["B", "A", "C"]})
pd.merge(left, right, on="id", how="inner") # id 2, 3
pd.merge(left, right, on="id", how="left") # id 1, 2, 3 (id 1의 grade는 NaN)
pd.merge(left, right, on="id", how="right") # id 2, 3, 4 (id 4의 sales는 NaN)
pd.merge(left, right, on="id", how="outer") # id 1, 2, 3, 4| how | 남기는 행 | 결과 id |
|---|---|---|
inner (기본값) | 양쪽 모두 있는 key만 | 2, 3 |
left | 왼쪽 표는 모두 유지 | 1, 2, 3 |
right | 오른쪽 표는 모두 유지 | 2, 3, 4 |
outer | 한쪽에만 있어도 유지 | 1, 2, 3, 4 |
groupby로 요약표 만들기
14쪽
summary = (
df.groupby("dept", as_index=False)
.agg(sum_sales=("sales", "sum"),
avg_sales=("sales", "mean"),
n=("sales", "count"))
)
# dept sum_sales avg_sales n
# 0 A 300 150.0 2
# 1 B 95 95.0 1
# 2 C 88 88.0 1미니 프로젝트: 매출 데이터 정리 파이프라인
15쪽
orders = pd.DataFrame({
"order_id": ["o1", "o2", "o3"],
"customer_id": ["c1", "c2", "c1"],
"amount": [120, 95, 180],
})
customers = pd.DataFrame({
"customer_id": ["c1", "c2", "c3"],
"region": ["Seoul", "Busan", "Daegu"],
})
result = (
orders.merge(customers, on="customer_id", how="left") # ① 주문에 지역 붙이기
.query("amount >= 100") # ② 100 이상만
.sort_values("amount", ascending=False) # ③ 큰 순서로
.groupby("region", as_index=False)["amount"].sum() # ④ 지역별 합계
)
# region amount
# 0 Seoul 300자주 나는 오류와 디버깅 순서
16쪽
| 오류·증상 | 주요 원인 | 확인 코드 |
|---|---|---|
KeyError | 열 이름 오타·공백 | df.columns |
| 빈 결과 | 조건식과 실제 값 불일치 | df["열"].unique() |
| 정렬 이상 | 숫자가 문자열 | df.dtypes |
| merge 후 행 폭증 | key 중복 | value_counts() |
SettingWithCopyWarning | 필터한 부분 복사본을 수정 | .loc 사용 |
print(df.columns) # 1. 열 이름
print(df.dtypes) # 2. 자료형
print(df.isna().sum()) # 3. 결측치
print(df["id"].value_counts().head()) # 4. key 중복핵심 문법 요약
17쪽
| 구간 | 문법 |
|---|---|
| 생성 | pd.DataFrame(data), pd.read_csv("file.csv") |
| 확인 | df.head(), df.info(), df.shape |
| 선택 | df["col"], df[["a", "b"]], df.loc[rows, cols] |
| 필터 | df[df["x"] >= 10], df.query("x >= 10") |
| 정렬 | df.sort_values("x"), df.sort_index() |
| 삭제·수정 | df.drop(columns=["x"]), df.rename(columns={"a": "b"}) |
| 병합 | pd.concat([a, b]), a.merge(b, on="id", how="left") |
| 요약 | df.groupby("g").agg(...), df.value_counts() |
정답과 해설
1. df[["name"]]은 열이 하나인 DataFrame, df["name"]은 Series다. 대괄호를 두 겹 쓰면 “열 목록”으로 고른 것이라 표 모양이 유지된다.
2. 4행(id 1, 2, 3, 4). 한쪽에만 있는 id도 모두 남는다.
3. 예시 답안:
result = (
orders.merge(customers, on="customer_id", how="left")
.query("order_date.dt.year == 2026")
.groupby("customer_id", as_index=False)["amount"].sum()
.sort_values("amount", ascending=False)
)정렬은 합계를 구한 뒤에 해야 “고객별 합계의 내림차순”이 된다. query 대신 [lambda d: d["order_date"].dt.year == 2026]나, 미리 만든 마스크로 걸러도 된다. 날짜가 문자열이면 먼저 pd.to_datetime()으로 바꾼다.
플래시카드
보충 Pandas — DataFrame, Series, Index의 관계는?
DataFrame은 표 전체, Series는 한 열(한 줄), Index는 행의 주소(행 이름)다.
보충 Pandas — 데이터를 읽은 직후 점검하는 3단계는?
① 열 이름(공백·대소문자·오타) ② 자료형(숫자·날짜가 문자열인지) ③ 결측치. 코드로는 df.columns, df.dtypes/info(), df.isna().sum().
보충 Pandas — df["a"], df[["a", "b"]], df[조건]의 차이는?
열 하나(Series), 열 여러 개(DataFrame), 조건이 True인 행만 남긴 행 필터.
보충 Pandas — 여러 조건 필터에서 가장 흔한 실수는?
비교식마다 괄호를 빼먹는 것. (df.a == 1) & (df.b >= 2)처럼 괄호로 묶고, and/or/not 대신 &, |, ~를 쓴다.
보충 Pandas — concat과 merge의 차이는?
concat은 같은 구조의 표를 위아래·좌우로 단순히 붙이고, merge는 공통 key 열을 기준으로 서로 다른 정보를 짝지어 연결한다.
보충 Pandas — merge의 how 네 가지는?
inner(양쪽 모두 있는 key만, 기본값), left(왼쪽 모두 유지), right(오른쪽 모두 유지), outer(한쪽에만 있어도 유지). 짝 없는 칸은 NaN.
보충 Pandas — groupby의 Split-Apply-Combine이란?
같은 값끼리 나누고(split), 그룹마다 계산하고(apply), 결과를 한 표로 합친다(combine).
보충 Pandas — merge 후 행 수가 갑자기 늘었다면?
key 중복을 의심한다. 병합 전후 행 수와 value_counts()로 key별 건수를 확인한다.
핵심 점검
- DataFrame에서 한 열을 꺼내면 가 된다.
- 필터는 조건식 → True/False → True인 행만 남기는 흐름이다.
between(a, b)는 양 끝을 한다.sort_values(..., ascending=False)는 정렬이다.- 공통 key로 두 표를 연결하는 함수는 , 단순히 붙이는 함수는 이다.
how를 생략한 merge는 조인이다.- merge 후 행이 늘면 을 의심한다.
내용을 이해했다면 학습 완료를 체크해 주세요.