Today I learned
1. QAQC 문제풀이 가이드
1) 데이터셋 정보 확인
import pandas as pd
data = {'Name': ['Alice', 'Bob', 'Charlie', 'David'],
'Age': [25, 30, None, 22],
'City': ['Seoul', 'Busan', 'Seoul', 'Jeju'],
'Score': [85, 92, 78, 95]}
df = pd.DataFrame(data)
df.info()
2) 데이터셋 통계 요약
import pandas as pd
data = {'Name': ['Alice', 'Bob', 'Charlie', 'David'], 'Age': [25, 30, None, 22], 'City': ['Seoul', 'Busan', 'Seoul', 'Jeju'], 'Score': [85, 92, 78, 95]}
df = pd.DataFrame(data)
print(df.describe())
3) 데이터셋 상위 3개 행 확인
import pandas as pd
data = {'Name': ['Alice', 'Bob', 'Charlie', 'David'], 'Age': [25, 30, None, 22], 'City': ['Seoul', 'Busan', 'Seoul', 'Jeju'], 'Score': [85, 92, 78, 95]}
df = pd.DataFrame(data)
print(df.head(3))
4) 키가 180 이상인 사람들의 데이터 가져오기
import pandas as pd
data = {
"이름": ["철수", "영희", "민수", "지영", "현우"],
"키(cm)": [175, 182, 168, 190, 177],
"몸무게(kg)": [70, 85, 60, 95, 72]
}
df = pd.DataFrame(data) # 데이터 프레임을 만들고
df_ki = df[df["키(cm)"] >= 180] # 필터링(우항)된 결과를 새로운 변수 df_ki에 저장한거임
print(df_ki)
5) 키와 몸무게를 이용하여 BMI와 비만 여부 칼럼을 추가하기
import pandas as pd
data = {
"이름": ["철수", "영희", "민수", "지영", "현우"],
"키(cm)": [175, 182, 168, 190, 177],
"몸무게(kg)": [70, 85, 60, 95, 72]
}
#데이터 프레임 만들기
df = pd.DataFrame(data)
#pandas에서 새로운 열을 만들 때 df["새로운컬럼"]형태를 쓰면 가로로 새로운 열이 붙음
df["BMI"] = df["몸무게(kg)"] / ((df["키(cm)"] / 100)**2)
#map(): BMI 값을 하나씩 꺼내서 lambda 함수에 전달
#lambda x: 임시 함수, x는 각 BMI의 값
df["비만여부"] = df["BMI"].map(lambda x: "정상" if x < 25 else "비만")
print(df)
6) 키, 몸무게, BMI 간 상관관게 매트릭스 출력하기
import pandas as pd
data = {
"이름": ["철수", "영희", "민수", "지영", "현우"],
"키(cm)": [175, 182, 168, 190, 177],
"몸무게(kg)": [70, 85, 60, 95, 72]
}
#데이터 프레임 만들고
df = pd.DataFrame(data)
#BMI 열을 추가
df["BMI"] = df["몸무게(kg)"] / ((df["키(cm)"]/100)**2)
#숫자형 열을 선택
numeric_cols = ['키(cm)','몸무게(kg)','BMI']
#상관계수를 구하려면 숫자만 필요, 숫자 컬럼 이름들만 리스트로 지정
correlation_matrix = df[numeric_cols].corr()
#df[numeric_cols]: df에서 숫자형 열만 추출(키, 몸무게, bmi), .corr(): 열과 열 사이 상관계수를 계산
print(correlation_matrix)
2. (라이브세션) Python 라이브러리 세션 2회차
1) 함수
특정 작업을 묶어 이름 붙인 코드블록
정의(def) - 호출(call) - 결과(return/print)
※ 함수 내에 print를 넣게 되면 재사용 할 수 없음
※print는 '보여주기' return은 '넘겨주기
ex1) print를 쓴 경우
def plus1(a,b):
print(a + b)
x = plus1(3,4)
print(x)
7 None
plus1(3,4)에 의해 함수 내의 print(a+b) 대한 결과값은 찍히지만, 정작 7이 나올 것으로 기대되는 x 자체는 값이 없음.
ex2) return을 쓴 경우
def plus2(a,b):
return a + b
x = plus2(3,4)
print(x)
print(x * 10)
7 70
plus2(3,4)에 의해 x에 7이라는 값이 넘겨졌고, x에 7이라는 값이 저장되었으므로 추가적인 연산이 가능함
ex3) 함수에 괄호 안붙이면 어떻게돼요?
def introduce():
print("나다 이 야")
introduce #이거 자체는 '함수 객체를 가리키는 참조'라서 값이 안나옴
introduce() #괄호를 붙여야 '실행한다'라는 뜻임
ex4) 함수 내의 값을 매개변수로 받아 재사용
def introduce(name = "어떤 가 재수없게 울고 지랄이야!"): #기존 name에 값이 지정된 상황
print(f"{name} 속이 후련했냐")
introduce(name = "그렇게 다 가져가야만") #name 내에 새로운 매개변수를 지정
새로 지정된 매개변수로 값이 도출
2) 매개변수
앞서 말한 것 처럼, print = 보여주기, return = 값을 돌려주기 임을 기억하자.
데이터 파이프라인에선 return이 재사용에 유리하다.
숫자 연산의 경계값, 예외 처리를 항상 의식
ex) 나누기 0
def substract(a,b):
print(f"{a}-{b}={a-b}")
def multiply(a,b):
print(f"{a} x {b} = {a*b}")
def divide(a,b):
if b == 0: #예외 처리에 대한 항목
print("0으로 나눠지겠냐?")
return None
print(f"{a} ÷ {b} = {a/b}")
ex) 예외 처리를 까먹으면?
def divide1(a,b):
print(f"{a} ÷ {b} = {a/b}")
divide1(10,0)
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Cell In[56], line 4 1 def divide1(a,b): 2 print(f"{a} ÷ {b} = {a/b}") ----> 4 divide1(10,0) Cell In[56], line 2 1 def divide1(a,b): ----> 2 print(f"{a} ÷ {b} = {a/b}") ZeroDivisionError: division by zero
요로코롬 에러가 나와용
3) return 심화
return값은 함수 외부로 값을 내보내는 접점
반환값은 또 다른 계산의 입력으로 이어질 수 있음.
ex) return을 알차게 사용하기
def stats(a,b):
s = a + b
d = a - b
return s, d
total, diff = stats(10, 3)
print("total = ", total, "diff = ", diff)
4) 모듈
모듈은 함수들을 보관, 배포하는 상자를 애기함
모듈에는 if __name__ == "__main__": 항이 포함되어야 함.
모듈을 직접 실행하면 __name__이 "__main__"이고, 다른 파일에서 import 하면 __name__ 이 "mycalc"가 된다.
ex) 모듈 실행 가이드
a. 모듈을 .py 파일 형식으로 입력한다. 같은 폴더에 들어있도록 위치시킨다. run python file로 실행시킨다.
b. 모듈에 입력한 인사 구문을 입력하여 잘 작동하는지 확인한다.
import mycalc
mycalc.인사()
c. 실행에 문제가 있을 경우엔 다음과 같은 방법을 사용한다.
c-1. 현재 작업 경로에 동일한 이름을 가진 다른 모듈이 있는지 확인한다.
c-2. 이전에 import한 모듈의 캐시가 남아있는 경우일 수 있다. 다음 식을 입력한다.
import importlib #importlib 내의 reload 함수를 사요하기 위해 불러오는 라이브러리
import mycalc #내가 지정한 모듈(여기서는 mycalc)를 불러오기
importlib.reload(mycalc) #메모리에 있는 모듈을 강제로 다시 읽어 갱신함
c-3. 꼬인 캐시가 남아있을 수 있으니 __pycache__를 삭제하고 다시 실행한다.
5) 클래스와 객체: 설계도와 제품
클래스는 설계도, 객체는 설계도로 찍어낸 실체를 말함
ex1) 개들을 풀어라
class Dog: #Dog라는 클래스(설계도)를 선언!
pass #아무 역할이 없지만 얘 안쓰면 문법오류남
d1 = Dog() #클래스를 호출
print(type(d1)) #호출한 d1의 타입을 보여줌
<class '__main__.Dog'>
ex2) 철수책상철책상
class Person: #person이라는 클래스를 정의
def __init__(self, name, age): #생성자 메소드, person 호출시 자동으로 실행됨
self.name = name #self: 현재 만들어지는 객체 자신, name과 age: 매개변수
self.age = age
def say_hello(self): #일반 메소드
print(f"{self.name} 안녕함. 나이는 {self.age}임")
p1 = Person("철수", 25) #p1 변수에 person 클래스 인스턴스를 저장
p1.say_hello()
철수 안녕함. 나이는 25임
6) 인스턴스/클래스/정적 메소드
6-1. 변수
| |
인스턴스 변수 |
클래스 변수 |
| 정의 |
개별 객체에 소속된 변수 |
클래스 자체에 소속된 변수로, 모든 인스턴스가 공유함 |
| 저장 위치 |
객체마다 별도의 __dict__에 저장 |
클래스 네임스페이스에 저장 |
| 선언 |
__init__ |
클래스 본문 내에서 바로 선언 |
| 공유 여부 |
인스턴스 별로 다른 값 |
인스턴스들이 같은 값을 참조 |
※클래스 네임스페이스: 클래스를 저장할 때 만들어지는 별도의 딕셔너리
6-2. 메소드
| |
인스턴스 메소드 |
클래스 메소드 |
정적 메소드 |
| 정의 |
일반적인 메소드, 인스턴스에서 호출 |
클래스 자체에서 호출할 수 있는 메소드 |
클래스와 인스턴스 둘 다와 독립적인 도우미 함수 |
데코레이터
|
|
@classmethod |
@staticmethod |
| 첫 번째 인자 |
self |
cls |
self, cls 둘다 필요 없음 |
| 용도 |
인스턴스 변수 접근 혹은 변경 |
클래스 변수 접근/변경, 인스턴스 생성 로직 커스터마이징 |
클래스, 인스턴스와 관계 없는 단순 기능 |
※ 데코레이터: 함수를 감싸 새로운 기능을 덧붙이는 도구로, @이름 형태의 문법을 사용
ex) 차 사고 싶어요
class Car:
factory = "Seoul Plant"
count = 0
#Car 라는 클래스(설계도)를 선언함
#factory, count는 클래스 변수로, 모든 인스턴스가 공유
#생성자
def __init__(self, model):
self.model = model # 인스턴스 변수
Car.count += 1 # 인스턴스가 하나 생성 될 때 마다 누적 +1
#인스턴스 메소드
def spec(self): # 첫 인자로 self를 받음
print(f"[{self.model}] from {Car.factory}") #factory를 Car.factory로 참조하고 있음
#클래스 메소드1
@classmethod # 첫 인자를 cls(클래스 자신)으로 받도록 하는 데코레이터
def made(cls): # made: 지금까지 만들어진 갯수를 cls.count로 돌려줌
return cls.count
#클래스 메소드2
@classmethod
def move_factory(cls, new_name): # move_factory: 공장 이름을 바꿈: 모든 인스턴스가 공유하는 클래스 변수 factory가 변경됨
cls.factory = new_name # 클래스 변수 변경
#정적 메소드
@staticmethod #self/cls를 받지 않는 보조 함수를 클래스 내에 넣음
def is_valid_model(name):
return isinstance(name, str) and len(name) >= 2 #name이 문자열이고, 길이가 2 이상이면 True
#인스턴스 생성
s1 = Car("Sonata")
s2 = Car("Ray")
print(Car.made()) # 두개의 인스턴스를 만듦, count = 2
s1.spec() # -> [Sonata] from Seoul Plant
s2.spec() # -> [Ray] from Seoul Plant
Car.move_factory("Ulsan Plant") # 클래스 변수를 Ulsan Plant로 변경
s2.spec() # -> [Ray] from Ulsan Plant
7) 실습
1) 클래스 문제
class Student:
def __init__(self, name, student_id): # __init__: 생성자, student로 객체를 만들 때 초기값을 설정
self.name = name # 인스턴스 변수1 (각 객체가 개별로 갖는 데이터)
self.student_id = student_id # 인스턴스 변수 2
def introduce(self): #introduce라는 함수를 만들 때
print(f"저는 {self.name}, 학번은 {self.student_id}입니다.")
#테스트
s = Student("지훈", "202501")
s.introduce()
3. (라이브세션) [데이터 전처리& 시각화] Pandas 고급
1) 결측치 처리 뫄스털
결측치: 데이터에서 값이 없거나 누락된 상태, 제대로 처리하지 않으면 분석 결과가 왜곡되거나 오류가 발생할 수 있음!
그럼 왜 생기는가?
a. 데이터의 수집 과정의 문제: 미응답, 센서 오류
b. 데이터의 입력 실수
c. 시스템적 결측: 특정 조건에서 데이터가 수집되지 ㅇ낳음
d. 의도적 결측
결측치가! 어디에! 있는지! 정확하게! 파악해야! 한다!
ex) My heart will go on
ex-1. 파일 불러오기
import pandas as pd
#외부 url에서 오픈소스 데이터를 가져옴
titanic = pd.read_csv(url)
titanic.isnull() #결측치만 True로 반환
891 rows × 12 columns
ex-2. 컬럼 별 결측치 계산
# 결측치 비율 계산
print("컬럼별 결측치 계산")
missing_ratios = (titanic.isnull().sum() / len(titanic)* 100).round(2)
missing_ratios
PassengerId 0.00
Survived 0.00
Pclass 0.00
Name 0.00
Sex 0.00
Age 19.87
SibSp 0.00
Parch 0.00
Ticket 0.00
Fare 0.00
Cabin 77.10
Embarked 0.22
dtype: float64
print("=== 컬럼별 결측치 개수 ===")
missing_counts = titanic.isnull().sum()
print(missing_counts)
=== 컬럼별 결측치 개수 ===
PassengerId 0
Survived 0
Pclass 0
Name 0
Sex 0
Age 177
SibSp 0
Parch 0
Ticket 0
Fare 0
Cabin 687
Embarked 2
dtype: int64
# 결측치 비율 계산 (더 중요!)
print("=== 컬럼별 결측치 비율 ===")
missing_ratios = (titanic.isnull().sum() / len(titanic) * 100).round(2)
#데이터프레임 만들기
missing_summary = pd.DataFrame({
'결측치_개수': missing_counts,
'결측치_비율(%)': missing_ratios
})
# 결측치가 있는 컬럼만 표시
missing_summary = missing_summary[missing_summary['결측치_개수'] > 0] #각 컬럼별 결측치가 0보다 큰지 True/False
print(missing_summary.sort_values('결측치_비율(%)', ascending=False)) #결측치 비율 순으로 정렬하고 출력
titanic.dropna() # 결측치를 제거하라.
183 rows × 12 columns
결측치를 제거하기 전 891 rows × 12 columns 였는데, 너무 많이 날아간거 아닌가???
ex-3. 결측치를 삭제했을 때 손실율 계산
titanic_dropped_all = titanic.dropna()
titanic_dropped_all.shape
# 결측치가 있는 행 모두 삭제
titanic_dropped_all = titanic.dropna()
print(f"모든 결측치 행 삭제 후: {titanic_dropped_all.shape}")
print(f"삭제된 행: {len(titanic) - len(titanic_dropped_all)}개")
print(f"데이터 손실률: {(1-len(titanic_dropped_all)/len(titanic))*100:.1f}%")
모든 결측치 행 삭제 후: (183, 12) 삭제된 행: 708개 데이터 손실률: 79.5%
사용 할 때를 구분하자!
a. 결측치 비율이 5% 미만일 때
b. 결측치 패턴이 완전 무작위일 때
c. 결측치를 제하고도 충분하 데이터가 남을 때
컬럼을 삭제해야 하는 경우는
a. 결측치 비율이 너무 많을 때(50% 이상)
b. 분석에 중요하지 않은 컬럼일 때
c. 다른 변수로 대체 가능한 정보를 담은 컬럼이 있을 때
#특정 칼럼의 결측치만 있는 행 삭제
titanic_age_dropped = titanic.dropna(subset = ['Age'])
# 내가 사용할 데이터 프레임을 가져와서 Age 카럼에 대해 결측치가 있는 항만 제거할거야
titanic_age_dropped
# subset 옵션: 필요한 항 만 제거할 때
#칼럼 제거
a = titanic.drop(columns = ['Cabin'])
a
#Cabin 칼럼만 없어진 걸 볼 수 있음
dropna와 subset을 구분하자
dropna: 결측치가 있는 행이나 열을 전부 제거
subset: 선택한 결측치 행을 제거
결측치 대체 전략 선택
- 평균값: 정규분포에 가까운 연속형 변수
- 중위수: 이상치가 많거나 치우친 분포
※ 이상치: 갑자기 높거나 낮게 튄 데이터
- 최빈값: 범주형 변수
- 특정값: 비즈니스 로직 상 의미가 있는 값
- 예측값: 다른 변수들로 예측한 값
# 원본 데이터 보존을 위한 복사
titanic_filled = titanic.copy()
# Age의 기본 통계 확인
print("=== Age 기본 통계 ===")
age_stats = titanic['Age'].describe()
print(f"평균: {age_stats['mean']:.1f}세")
print(f"중위수: {age_stats['50%']:.1f}세")
print(f"표준편차: {age_stats['std']:.1f}")
print(f"결측치: {titanic['Age'].isnull().sum()}개")
=== Age 기본 통계 === 평균: 29.7세 중위수: 28.0세 표준편차: 14.5 결측치: 177개
# 평균값으로 대체
age_mean = titanic_filled['Age'].mean()
titanic_filled['Age_mean_filled'] = titanic_filled['Age'].fillna(age_mean)
titanic_filled['Age_mean_filled']