데이터 셋 준비(캐글 - 타이타닉 데이터셋 활용)
🔗 실습 링크 : https://www.kaggle.com/datasets/vinicius150987/titanic3
데이터 불러오기
import pandas as pd
# 원본 파일 형식에 맞춰서 read_excel로 데이터 불러오기
titanic_df = pd.read_excel('titanic3.xls')
titanic_df
※ 간혹, `.xls`파일을 Jupyter Notebook에서 불러올 때 `ModuleNotFoundError : No module named 'xlrd'` 에러가 발생할 수 있다. 이는 문장 그대로 해당 모듈이 없기 때문에 발생하는 에러인데, 다음 처럼 해결할 수 있다.
1) Anaconda Navigator의 [Environment] 탭으로 들어간다.
2) 우측 상단 검색창에 "xlrd"를 검색한다.
3) 상단에 'Installed'로 되어있다면 'Not Installed'로 바꿔준 후 'xlrd'의 체크박스를 클릭해서 'Apply'를 실행한다.
4) 설치가 끝나면 *openssl과 *certifi 와 같은 모듈을 같이 추천해주는데, 이것들은 해당 버전을 사용하기 위해서 연결된 모듈들이기 때문에 이 점을 알아두고 'Apply'를 눌러서 넘어가면 된다.
필자는 이미 모듈이 설치되어져 있었기 때문에 아래와 같이 화면으로만 보여주도록 하겠다.
데이터 확인하기
해당 데이터셋의 목표
타이타닉 호 승객들의 데이터(ex. 이름, 나이, 성별, 사회/경제적 지위 등)를 사용하여
어떤 타입의 사람들이 타이타닉호 침몰 사건에서 생존할 가능성이 더 높았는가? 라는 예측 모델을 구축하도록 요청한다.
예상해볼 수 있는 분석
- 생존율
- 승객들의 특성(ex. 성별, 나이, 객실 등급 등)에 따른 생존율 분석이 가능하다.
- 어떤 그룹의 사람들이 생존할 가능성이 높/낮은지, 그들의 특징은 무엇인지 파악 가능하다. - 특성 엔지니어링
- 승객들의 특성을 활용하여 '새로운 특성'을 만들어내거나, 범주형 특성을 '수치형 특성'으로 변환할 수 있다. - 데이터 시각화
- 데이터셋을 시각화하여 생존 여부와 관련된 패턴이나 상관관계를 탐색하고 데이터를 이해할 수 있다. - 예측 모델링
- 생존여부를 예측하는 머신러닝 모델을 개발할 수 있다.
- 다양한 알고리즘을 사용하여 모델을 학습 및 테스트하고 이로부터 최적의 모델을 찾을 수 있다. - 특성 중요도 분석
- 학습된 모델을 통해 각 특성의 중요도를 분석할 수 있다.(어떤 특성이 생존 예측에 가장 중요한 역할을 하는지 등)
컬럼 정보
컬럼명 | 의미 | 범주 |
pclass | 승객의 등급 : 사회적 지위(SES)를 나타내는 대리변수 |
- 1 : 1등급(상류층) - 2 : 2등급(중산층) - 3 : 3등급(하류층) |
survived | 생존 여부 | - 0 : 사망 - 1 : 생존 |
name | 승객 이름 | |
sex | 승객 성별 | |
age | 승객 나이 (1보다 적은 경우 소수점으로 표현) |
|
sibsp | 동승한 형제자매/배우자 인원수 (일부 관계 무시) |
|
parch | 동승한 부모/자녀 인원수 | |
ticket | 티켓 번호 | |
fare | 승객 요금 (영국 파운드) |
|
cavin | 객실 번호 | |
embarked | 탑승 항구 | - C : 쉐르부르 - Q : 퀸스타운 - S : 사우샘프턴 |
boat | 구명보트 번호 | |
body | 시신 식별 번호 | |
home.dest | Home/Destination : 승객의 거주지/목적지 |
데이터 프레임 준비
1. 인덱스 지정 index_cols=
# 실습을 위해 name을 인덱스로 사용할 예정
# index_col = 'name'으로 지정해보자
titanic_df = pd.read_excel('titanic3.xls', index_col='name')
titanic_df.head()
2. 정렬 .sort_index()
파라미터 지정 없이 정렬할 경우 문자부호표(ASCII) 원리로 소문자가 대문자보다 뒤쪽으로 배치된다.
따라서 대소문자 처리 방식을 정하는 함수를 지정하는 `key=` 파라미터를 사용한다.
# 정렬 : 이름순 - 대소문자 상관없이 정렬(전부 소문자화 시킨 후 정렬)
# key : 크고 작음을 처리하는 방식을 정하는 함수 지정(lambda로)
titanic_df = titanic_df.sort_index(key=lambda x : x.str.lower())
titanic_df
행 조회 .loc[행]
<입력 방식>
- 하나의 레이블
ex) `5` 또는 `'a'` (※ 단, `5`는 인덱스 레이블로 해석되며 정수 위치로 해석되진 않음) - 레이블의 리스트 또는 배열
ex) `['a', 'b', 'c']` - 레이블 범위로 된 슬라이스 객체 (※ 끝 객체까지 포함해서 조회)
ex) `'a' : 'f'` - 축과 길이가 같은 boolean 배열
ex) `[True, False, True]`
📌 인덱스에는 Index Label, RangeIndex, 정수 Index가 따로 따로 존재하는데, 여기서 우선순위는 Index Label이 가장 높으며 이를 사용하는 것을 지향해야 한다.
즉, 숫자를 사용할 수 있는 상황이더라도 Index Label을 사용하는 것이 좋다.
행의 열 조회 .loc[행, 열]
예제1. Blank, Mr. Henry의 승객 등급(pclass)와 요금(fare)을 DataFrame으로 가져오기
# 인덱스도 리스트로 감싸줘야 한다.
titanic_df.loc[['Blank, Mr. Henry'], ['pclass', 'fare']]
예제2. Herman, Miss. Alice와 Herman, Miss. Kate의 승객 정보를 DataFrame으로 가져오기
# 탑승자 두 명을 하나의 리스트에 담기
passenger = ['Herman, Miss. Alice', 'Herman, Miss. Kate']
# 승객 정보 조회
titanic_df.loc[passenger, ['pclass', 'ticket', 'cabin', 'boat', 'home.dest']]
▶ 많은 정보가 일치하는 것으로 보아, 이 두명의 승객은 가족관계에 있을 가능성이 매우 높다는 것을 알아냄.
동명이인 대처 .duplicated()
: 데이터프레임 내 중복된 데이터가 있는지 없는지 여부를 Boolean Series 형태로 반환한다.
(ex. `array([False, False, True, ...])`)
<파라미터>
- `subset`: 대상이 되는 컬럼 혹은 리스트 지정
- `keep` : 중복값에서 처음 값과 끝 값 중 어떤 것을 남길지 여부
- `'first'` : 첫 번째 등장 값을 제외하고, 이후에 등장하는 중복 값들은 `True`로 반환← 디폴트
- `'last'` : 마지막 등장 값을 제외하고, 이전에 등장하는 중복 값들은 `True`로 반환
- `False` : 어떤 값을 남길지 고려하지 않고, 모든 중복 값들을 `True`로 반환
# 모든 중복값을 포함하고 싶을 때
titanic_df.loc[titanic_df.index.duplicated(keep=False)]
인덱스 위치 기반 행과 열 조회 .iloc[행, 열]
.iloc[] 조회 방법에 따른 결과 데이터 타입
- `.iloc[index_position]` → Series
- `.iloc[[index_position]]` → DataFrame
- `.iloc[[index_position1, index_position2, index_position3]]` → DataFrame
- `.iloc[index_position, column_position]` → 스칼라(단독 값)
- `.iloc[[index_position], column_position]` → Series
- `.iloc[[index_position], [column_position]]` → DataFrame1
- `.iloc[index_position, [column_position]]` → Series
★ Series의 `.squeeze()` 함수는 차원 축소 함수
- DataFrame --차원축소--> Series
- Series 또는 1행1열만 있는 DataFrame --차원축소--> 스칼라(단독 값)
loc[ ] vs iloc[ ]
.loc[ ] | .iloc[ ] |
index label(레이블)을 참조하여 행 선택 | index position(위치)을 참조하여 행 선택 |
문자, 숫자 모두 가능 | 고정(파이썬의 리스트 인덱스와 유사함) |
n번째 행을 삭제하고 `.loc[n]`으로 찾으면 해당 위치에 있는 값이 없어진 것이므로 KeyError 발생 |
n번째 행을 삭제하고 `.iloc[n]`으로 찾아도 그 다음 위치에 있는 n+1번째 행을 조회함(에러X) |
▶ 가능하면 index label의 숫자는 피하는 것이 좋다.
▶ index의 중복값이 우려된다면 그 때는 index label이 정수여도 괜찮음
▶ 편하게 사용할 수 있는 함수는 `.iloc`
▶ But, 유의미하고 명확하게 사용할 수 있는 함수는 `.loc` 이므로 "특정 행에서 특정 처리"를 해야 하는 상황에서는 `.loc`을 지향
서로 다른 차원의 배열 연산 - Broadcasting & Matching
: (numpy에서는) Array, (Pandas에서는) DataFrame, Series의 모양이 다를 경우 연산이 가능하도록 모양을 처리하는 방법
들어가기 전에 ..
Numpy의 Broadcasting
- Pandas의 Broadcasting과 진행 방식이 다르다.
- Numpy의 대부분의 연산은 '수치 연산'이기 때문에 수학과 관련된 기법으로 처리한다.
- Stretch : 데이터가 있는 것처럼 하나의 값을 없는 데이터에 채우는 방식
Pandas의 Broadcasting
- 각각 처리하고 반영이 어려운 곳은 결측치(NA)로 처리한다.
- Pandas는 Numpy와 달리 여러 형태의 데이터가 올 수 있기 때문에 Stretch 방식을 사용하지 않는다.
EX)
import pandas as pd
a = pd.Series(data = [1, 2, 3])
b = pd.Series(data = 2)
a * b
a + b
Matching & Broadcasting
1. 행렬 크기가 같을 때
문제 없이 모두 작동한다.
a = pd.Series(data = [1, 2, 3])
b = pd.Series(data = [3, 5, 7])
a / b
2. 행렬 크기가 다를 때
차원은 같지만 크기가 다른 행렬끼리의 연산
Case1. 크기가 다른 Series를 DataFrame화 시킬 때
s1 = pd.Series(data = [1, 2, 3], index=['a', 'b', 'c']) # 'd'가 비어있음
s2 = pd.Series(data = [4, 5, 6, 7], index=['a', 'b', 'c', 'd'])
s3 = pd.Series(data = [8, 9, 10], index=['a', 'b', 'd']) # 'c'가 비어있음
df1 = pd.DataFrame(
data = {'1열' : s1, '2열' : s2, '3열' : s3}
)
df2 = pd.DataFrame(
data = {'2열' : s2}
)
# 매칭이 안되는 곳은 결측치로 처리한다.
df1
df2
Case2. 모두 DataFrame이고 행렬 크기가 다르지만 동일한 열이름이 있는 경우 사칙 연산은?
→ 동일한 label만 가진 열만 계산되고, 나머지는 모두 결측치(NaN)로 반환됨
df1 - df2
Case3. DataFrame과 Series의 연산 (열 방향 연산)
# 1. 차원 축소가 필요하다
# df1의 2열 조회와 동일함 : df1['2열']
s4 = df2.squeeze()
s4
# 축 설정을 해주지 않으면 옆으로 붙어버린다.
df1 - s4
► Series는 방향이 없기 때문에 (시작점과 끝점만 존재) 연산시, 방향(축)을 설정해줘야 한다.
# sub 함수로 축을 변경해주면 Stretch가 일어난다
df1.sub(s4, axis='index')
Case4. DataFrame과 Series의 연산 (행 방향 연산)
별다른 방향(축) 설정을 해주지 않으면 위아래로 Stretch가 일어난다.
s5 = df1.iloc[2]
s5
df1.sub(s5)
📌 축을 이용할 때 사용해야하는 메소드
연산자 | 메소드 | 사용예시 |
+ | .add() | `df.add(row, axic='columns')` `df.add(row, axic='index')` |
- | .sub() | `df.sub(row, axic='columns')` `df.sub(row, axic='index')` |
* | .mul() | `df.mul(row, axic='columns')` `df.mul(row, axic='index')` |
/ | .div() | `df.div(row, axic='columns')` `df.div(row, axic='index')` |
- Series화 하여 연산하면 Stretch가 일어남
- DataFrame끼리 연산하면 Stretch가 일어나지 않고 그냥 결측치(NaN)처리됨
조건문 (Conditional Statement)
- Python에서는 True/False를 기반으로 실행 제어 : `if ~ else ~ matchcase`
- Pandas에서는 True/False로 만들어진 Series/DataFrame으로 조건에 맞는 데이터를 탐색 : 마스킹(Masking) 활용
1. == 를 이용한 조건문
# 생존자 찾기
titanic['survived'] == 1 # 시리즈 == 스칼라 >> stretch가 일어남 >> boolean형 결과
# 생존자 찾기 (DataFrame 형태로 반환되도록)
titanic[titanic['survived'] == 1]
2. mask를 생성해서 필터링하는 조건문
## 남자(male)만 필터링해서 조회해보자.
# 1) mask를 생성한다.
male_mask = titanic['sex'] == 'male'
female_mask = titanic['sex'] == 'female'
old_mask = titanic['age'] > 65
young_mask = titanic['age'] < 15
# 2) male_mask 확인
titanic[male_mask]
3. 비트 연산자를 이용한 다중 조건문(Multiple Conditions)
★ 비트 연산자(Bitwise Operators)
비트연산자 | 의미 |
& | and (둘다 참일때만 참) |
| | or (둘 중 하나만 참이어도 참) |
~ | not(참이면 거짓, 거짓이면 참) |
예제 1. 남성이면서 65세 이상의 생존자를 조회해보자.
# 1) mask 생성
male_mask = titanic['sex'] == 'male'
old_mask = titanic['age'] > 65
# 2) 우선 비트 연산자로 mask를 연산해보자 (broadcasting이 일어나지 않고 matching만 일어남)
male_mask & old_mask
# 3) mask로 필터링해서 조회
titanic[male_mask & old_mask]
예제 2. (15세 미만 또는 여성)의 생존자를 조회해보자.
# 1) mask 생성
female_mask = titanic['sex'] == 'female'
young_mask = titanic['age'] < 15
survived_mask = titanic['survived'] == 1
# 2) mask로 필터링해서 조회 (핵심은 소괄호를 이용하는 것)
titanic[(young_mask | female_mask) & survived_mask]
범위 지정 필터링 .Between(left, right, inclusive= )
: 주어진 범위 내 요소를 가진 Boolean Series를 반환하는 Series의 메소드
→ 각 요소가 `left`와 `right` 사이에 있다면 해당 위치에 `True`를 포함하는 Boolean Vector을 반환(결측치는 `False` 처리)
`시리즈명.between(left, right, inclusive= )`
- `left` : 왼쪽 경계값
- `right` : 오른쪽 경계값
- `inclusive=` : 경계값을 포함할지 여부를 지정하는 매개변수
- `'both'` : 왼쪽과 오른쪽 경계값을 모두 포함 ← 디폴트
- `'left'` : 왼쪽 경계값만 포함
- `'right'` : 오른쪽 경계값만 포함
- `'neither'` : 왼쪽과 오른쪽 경계값 모두 포함하지 않음
# 20 이상 30미만 mask 생성
age_20s_mask = titanic['age'].between(20, 30, inclusive='left')
titanic[age_20s_mask]
결측치 제어를 통한 조건문 단순화
1. 시리즈명.isin(값)
: Series의 각 요소가 주어진 값(values)에 포함되는지 여부를 Boolean Series로 반환
- values에 단일 문자열을 전달할 경우 `TypeError` 발생
# 상류층, 중산층인 사람들 필터링
titanic['pclass'].isin([1, 2])
2. 시리즈명.isnull()
: null이 있는지 확인하는 메소드로, NA를 발견하면 True를 반환
- `.isna()` 와 동일하게 동작
# 우선 null이 있는 데이터 확인하기
titanic.info()
# 개수 확인
titanic['age'].isnull().sum()
3. 시리즈명.notnull()
: NA가 아닌 데이터를 발견하면 True를 반환
# 개수 확인
titanic['age'].notnull().sum()
행렬, 인덱스 이름 변경 & 생성
# 열 이름을 변경해보자 pclass -> class
# Try : 수정 = 접근 후 할당 -> TypeError 발생
titanic.columns[0] = 'class'
.rename() 메소드
1) 컬럼명 변경하기 .rename(columns={'원래이름' : '변경된이름'})
titanic.rename(columns={'pcalss': 'class'}, inplace=True)
titanic
2) 인덱스명 변경하기 .rename(index={'원래이름' : '변경된이름'}, axis = )
# 인덱스에서 'Allen, Miss. Ellisabeth Walton' 이름 변경
titanic.rename({'Allen, Miss. Ellisabeth Walton' : 'Allen'}, axis=0)
.name 메소드
1) 인덱스명 생성하기 .index.name = '인덱스명'
titanic.index.name = 'Passengers'
titanic
2) 컬럼명 생성하기 .columns.name = '컬럼명'
titanic.columns.name = 'Information'
titanic