본문 바로가기

카테고리 없음

Upstage AI Lab 3기 - Python EDA

강사 : 김용담

- 서강대학교 컴퓨터공학과 학사, 석사
- 패스트캠퍼스 전속강사
- Kaggle Competition & Notebook Expert
- 데이터사이언스 관련 강의 5000시간 이상

 

목차

1) numpy

2) pandas

3) seaborn

4) EDA

 

▣ Numpy

Numerical computing with Python. 수치연산 및 벡터 연산에 최적화된 라이브러리.

 

  • 2005년에 만들어졌으며, 100% 오픈소스입니다.   
  • 최적화된 C code로 구현되어 있어 엄청나게 좋은 성능을 보입니다.
  • 파이썬과 다르게 수치 연산의 안정성이 보장되어 있습니다. (numerical stable)
  • N차원 실수값 연산에 최적화되어 있습니다. == N개의 실수로 이루어진 벡터 연산에 최적화되어 있습니다.

 

Numpy를 사용해야 하는 이유

 

  • 데이터는 벡터로 표현됩니다. 데이터 분석이란 벡터 연산입니다. 그러므로 벡터 연산을 잘해야 데이터 분석을 잘할 수 있습니다.
  • (native) 파이썬은 수치 연산에 매우 약합니다. 실수값 연산에 오류가 생기면 (numerical error) 원하는 결과를 얻지 못할 수 있습니다. 많은 실수 연산이 요구되는 머신러닝에서 성능 저하로 이어질 수 있습니다.
  • numpy는 벡터 연산을 빠르게 처리하는 것에 최적화되어 있습니다. 파이썬 리스트로 구현했을 때보다 훨씬 더 높은 속도를 보여줍니다.

1. Numpy Array and Operation

numpy array의 특징

  • 모든 원소의 dtype이 같다.
  • 연산의 의미가 조금 다르다. (broadcasting)
  • 대용량 array인 경우에 for문을 직접 사용하는 것보다 내부적으로 정의된 연산을 사용하는게 더 빠르다.
  • 생성 후에 크기 변경이 불가능하다.
# numpy 라이브러리
import numpy as np

# version 확인
np.__version__

 

# 파이썬 리스트 선언
data = [1,2,3,4]

# 파이썬 2차원 리스트(행렬) 선언
data2 = [[1,2],
         [3,4]]

 

- numpy array로 변환(1차원)

np.array(data)

실행)
array([1, 2, 3, 4])

- 2차원 리스트를 np.array

np.array(data2)

출력)
array([[1, 2],
       [3, 4]])

 

1. numpy array 생성방법

np.arange(0,10)
np.arange(0,10).shape # (10,) - 1차원 numpy array

np.zeros(shape=(5,)) # array([0., 0., 0., 0., 0.])

np.zeros(shape=(5,3))

# array([[0., 0., 0.],
#        [0., 0., 0.],
#        [0., 0., 0.],
#        [0., 0., 0.],
#        [0., 0., 0.]])

np.linspace(0,1,10) # 특정구간의 갯수만큼 균등 숫자 생성
# [start, stop]에서 num개의 number를 균등한 구간으로 잘라서 원소를 생성

# array([0.        , 0.11111111, 0.22222222, 0.33333333, 0.44444444,
#        0.55555556, 0.66666667, 0.77777778, 0.88888889, 1.        ])

np.random.randn(5,3)
# # Return a sample (or samples) from the "standard normal" distribution.(표준정규분포)

# array([[-1.59359743, -0.62665787, -1.22097732],
#        [ 0.89072018,  0.78366209,  0.35240815],
#        [ 1.15542566,  0.67757696,  0.61264326],
#        [ 1.05803722, -2.14297234, -0.84047931],
#        [ 0.0856095 , -3.09546886, -0.25720783]])

 

2. Reshaping array

# 1차원 : vector
# 2차원 : matrix
# 3차원 이상 : tensor
# shape은 가장 바깥 괄호부터 원소의 개수를 순차적으로 기록한 것으로 정의.
p.zeros(shape=(5,3,4))

# array([[0., 0., 0.],
#        [0., 0., 0.],
#        [0., 0., 0.],
#        [0., 0., 0.],
#        [0., 0., 0.]])

# 224 x 224 크기의 3개(RGB)의 channel을 가지고 있는 이미지가 32개.
np.zeros(shape=(32,3,224,224))

# reshape
np.arange(1,10).reshape(3,3)


np.arange(1,121).reshape(24, -1) # -1 알아서 계산해 나눠 준다.
np.arange(1,121).reshape(-1, 8)

 

3. Concatenation Arrays

 

더하기

arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])

arr1 + arr2

# array([5, 7, 9])

 

연결하기

np.vstack([arr1, arr2]) # 세로 

# array([[1, 2, 3],
#        [4, 5, 6]])

np.hstack([arr1, arr2]) # 가로

# array([1, 2, 3, 4, 5, 6])

 

4. Array 연산

v1 = np.array((1,2,3))
v2 = np.array((4,5,6))

v1 + v2

# array([5, 7, 9])

v1 - v2

# array([-3, -3, -3])

v1 * v2

# array([ 4, 10, 18])

v1 / v2

# array([0.25, 0.4 , 0.5 ])

v1 @ v2

# 32

 

5. Broadcasting, universal Function

서로 크기가 다른 numpy array를 연산할 때, 자동으로 연산을 전파(broadcast)해주는 기능

 

arr1 = np.array([1,2,3])

arr2 = np.array([[-1,-1,-1],
                 [1, 1, 1]])
                 
arr1 + arr2 # (3,) + (2,3) 크기가 같은 마지막 차수 것을 찾아 broadcasting

# array([[0, 1, 2],
#        [2, 3, 4]])


######################################
# (3,) + (3, 2) 는 계산 오류남.

# (5,) + (3,4,5) : 가능
# (3,4) + (5,3,4) : 가능
# (3,4) + (3,4,5) : 불가


arr1 * arr2

# array([[-1, -2, -3],
#        [ 1,  2,  3]])


# universal Function
arr1 / 1 # int -> float dtype변경 트릭으로 사용
# array([1., 2., 3.])

# Universal Function
1 / arr1 # 각 원소를 reverse연산.  array([1.        , 0.5       , 0.33333333])

# => 아래와 같다.
arr1 = np.array([1.,2.,3.])
def reverse_num(x):
    return 1/x

for i in range(len(arr1)):
    arr1[i] = reverse_num(arr1[i])

arr1

# array([1.        , 0.5       , 0.33333333])

 

6. Numpy method

 

- math

# pseudo-random(sw에서는 완벽한 random을 구현할 수 없어서...) : 현재 시간(ns)을 기준으로
np.random.seed(42)            # 고정된 랜덤을 생성하기 위해 seed번호 설정
mat1 = np.random.randn(5,3)
mat1

# array([[ 0.49671415, -0.1382643 ,  0.64768854],
#        [ 1.52302986, -0.23415337, -0.23413696],
#        [ 1.57921282,  0.76743473, -0.46947439],
#        [ 0.54256004, -0.46341769, -0.46572975],
#        [ 0.24196227, -1.91328024, -1.72491783]])

# 절대값
np.abs(mat1)

# array([[0.49671415, 0.1382643 , 0.64768854],
#        [1.52302986, 0.23415337, 0.23413696],
#        [1.57921282, 0.76743473, 0.46947439],
#        [0.54256004, 0.46341769, 0.46572975],
#        [0.24196227, 1.91328024, 1.72491783]])

# 제곱근
np.sqrt(mat1)

# array([[0.70477951,        nan, 0.80479099],
#        [1.23411096,        nan,        nan],
#        [1.25666734, 0.87603352,        nan],
#        [0.73658675,        nan,        nan],
#        [0.49189661,        nan,        nan]])

 

- 집계

np.random.seed(0xC0FFEE)    # 김용담 강사가 좋아하는 숫자
mat2 = np.random.rand(3, 2) # 0과 1사이 값.
mat2

array([[0.57290783, 0.81519505],
       [0.92585076, 0.09358959],
       [0.26716135, 0.96059676]])
       
np.sum(mat2)
np.sum(mat2, axis=0) # 세로방향(=같은column) 합
np.sum(mat2, axis=1) # 가로방향(=같은row) 합

np.mean(mat2, axis=0)

np.std(mat2, axis=1)

np.min(mat2)
np.max(mat2, axis=1)

# 최대값이 있는 Index
np.argmax(mat2, axis=0)

 

# numpy dtype (number representation)
print(f"int4 : -{2**3} ~ {2**3 - 1}")
print(f"int8 : -{2**7} ~ {2**7 - 1}")
print(f"int16 : -{2**15} ~ {2**15 - 1}")
print(f"int32 : -{2**31} ~ {2**31 - 1}")
print(f"int64 : -{2**63} ~ {2**63 - 1}")


# int4 : -8 ~ 7
# int8 : -128 ~ 127
# int16 : -32768 ~ 32767
# int32 : -2147483648 ~ 2147483647
# int64 : -9223372036854775808 ~ 9223372036854775807

 

 

▣ Pandas

Library 불러오기

import pandas as pd
pd.__version__

 

1. DataFrame

# 12x4 행렬, index는 0부터 시작하고, coulmns은 순서대로 X1, X2, X3, X4로 하는 DataFrame 생성
np.random.seed(42)
df = pd.DataFrame(data=np.random.randn(12,4),
                  index=np.arange(12),
                  columns=['X1', 'X2', 'X3', 'X4'])
                  
# dataframe index
df.index

# dataframe columns
df.columns

# 문자열 dtype='object'

df.values # DataFrame의 데이터를 np.array로 가져옴.

# 특정 column을 가져오기
df['X1']

# X1 column에 2 더하기
df['X1'] + 2 # broadcasting - numpy와 같다

 

2. Method

# dataframe의 맨 위 다섯줄을 보여주는 head()
df.head()

# 10줄
df.head(10)

# 뒤에서 부터 기본 5줄
df.tail()

# dataframe에 대한 전체적인 요약정보. index, columns, null/not-null/dtype/memory usage 표시.
df.info()

# dataframe에 대한 전체적인 통계정보
df.describe()

# X2 column를 기준으로 내림차순 정렬
df.sort_values(by='X2', ascending=False)

 

3. 팬시 인덱싱

# dataframe에 조건식을 적용해주면 조건에 만족하는지 여부를 알려주는 "mask"가 생깁니다.
# df에서 X3 column에 있는 원소들중에 양수만 출력하기
mask = df['X3'] > 0           ## boolean mask
df[ mask ]                    ## boolean mask를 DataFrame에 indexing을 하면 True에 해당하는 위치의 row들이 뽑힘.


df[ mask ] ['X3']  # 방법 1
## df.loc[row에 대한 조건식, col에 대한 조건식]
df.loc[mask, 'X3'] # 방법 2

##df에서 X1 column에 있는 원소들중에서 1보다 작은 원소들을 출력.
mask = df['X1'] < 1
df.loc[mask, 'X1']

# iloc로 2차원 indexing을 하게되면, row 기준으로 index 3,4를 가져오고 column 기준으로 0, 1을 가져옴.
df.iloc[[3,4],[0, 1]]

# 2차원 indexing에 뒤에가 : 
df.iloc[:, 3] # df['X4'] 와 같음
df.iloc[0, :] # 0 row

 

4. 데이터프레임 합치기

 

df1 = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
                    'B': ['B0', 'B1', 'B2', 'B3'],
                    'C': ['C0', 'C1', 'C2', 'C3'],
                    'D': ['D0', 'D1', 'D2', 'D3']},
                   index=[0, 1, 2, 3])

df2 = pd.DataFrame({'A': ['A4', 'A5', 'A6', 'A7'],
                    'B': ['B4', 'B5', 'B6', 'B7'],
                    'C': ['C4', 'C5', 'C6', 'C7'],
                    'D': ['D4', 'D5', 'D6', 'D7']},
                   index=[4, 5, 6, 7])

df3 = pd.DataFrame({'A': ['A8', 'A9', 'A10', 'A11'],
                    'B': ['B8', 'B9', 'B10', 'B11'],
                    'C': ['C8', 'C9', 'C10', 'C11'],
                    'D': ['D8', 'D9', 'D10', 'D11']},
                   index=[8, 9, 10, 11])
                   
# 위-아래로 그냥 합치기 (concatenation)
pd.concat([df1, df2, df3])         # axis=0, column을 기준으로 세로방향 합치기
pd.concat([df1, df2, df3], axis=1) # index를 기준으로 가로방향 합치기 

df2.reset_index(drop=True)  # VIEW - 원본에 적용되지 않음
# drop=True 기존 인덱스 삭제

# index를 맞춰 수정한 후 index를 기준으로 가로방향 합치기
pd.concat([df1, df2.reset_index(drop=True), df3.reset_index(drop=True)], axis=1)

 

# data와 인덱스 수정 .....
df1 = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
                    'B': ['B0', 'B1', 'B2', 'B3'],
                    'C': ['C0', 'C1', 'C2', 'C3'],
                    'D': ['D0', 'D1', 'D2', 'D3']})

df2 = pd.DataFrame({'A': ['A4', 'A5', 'A1', 'A2'],
                    'B': ['B4', 'B5', 'B6', 'B7'],
                    'C': ['C4', 'C5', 'C6', 'C7'],
                    'D': ['D4', 'D5', 'D6', 'D7']})

df3 = pd.DataFrame({'A': ['A8', 'A9', 'A10', 'A11'],
                    'B': ['B8', 'B9', 'B10', 'B11'],
                    'C': ['C6', 'C7', 'C10', 'C11'],
                    'D': ['D8', 'D9', 'D10', 'D11']})
                    
                    
# A inner join B --> table A와 table B의 특정 column에서 겹치는 값들을 기준으로 두 테이블을 합치는 연산.
pd.merge(left=df1,
         right=df2,
         on='A',
         how='inner')
         
         
# 	A	B_x	C_x	D_x	B_y	C_y	D_y
# 0	A1	B1	C1	D1	B6	C6	D6
# 1	A2	B2	C2	D2	B7	C7	D7


#df2 inner join df3 (column C)
pd.merge(left=df2, right=df3, on='C', how='inner')

# 	A_x	B_x	C	D_x	A_y	B_y	D_y
# 0	A1	B6	C6	D6	A8	B8	D8
# 1	A2	B7	C7	D7	A9	B9	D9


# left join : left table(df1)을 기준으로 right table(df2)에서 on에 대해서 겹치는 대상을 붙여준다.
# 겹치지 않는 데이터는 NaN으로 추가한다.
pd.merge(df1, df2, on='A', how='left')

# 	A	B_x	C_x	D_x	B_y	C_y	D_y
# 0	A0	B0	C0	D0	NaN	NaN	NaN
# 1	A1	B1	C1	D1	B6	C6	D6
# 2	A2	B2	C2	D2	B7	C7	D7
# 3	A3	B3	C3	D3	NaN	NaN	NaN

 

데이터 불러오기 with DataFrame

 

titanic = pd.read_csv('train.csv')

# 여성 승객들의 평균 나이
# 넘파이 함수
np.mean(titanic.loc[titanic.Sex == 'female', 'Age'])
# 판다스 자체 함수
titanic.loc[titanic.Sex == 'female', 'Age'].mean()


#1등석에 탑승한 승객 중 최대 요금을 낸 사람의 이름.
# titanic.Pclass == 1
# 방법1 : 내풀이
max_fare = titanic.loc[titanic.Pclass == 1, 'Fare'].max()

# titanic.loc[titanic['Fare'] == max_fare]['Name']
titanic.loc[titanic['Fare'] == max_fare, 'Name']

# 방법2
temp = titanic.loc[titanic.Pclass == 1, ['Name', 'Fare']]
temp.loc[temp.Fare == temp.Fare.max(), "Name"]


# 1등석에 탑승한 승객 중 승선한 곳이 (Embarked ) Queenstown인 사람의 수
# 내 풀이
titanic.loc[(titanic.Pclass == 1) & (titanic.Embarked == 'Q'), ['Name']].count()

# 강사풀이
titanic[(titanic.Pclass == 1) & (titanic.Embarked == 'Q')]

# mask & mask : 두 조건을 둘 다 만족하는 mask (AND)
# mask | mask : 두 조건 중 하나 이상 만족하는 mask. (OR)
# ~mask : 조건을 반전 (NOT)


#승선한 곳이 "S"인 사람들의 생존자 수
titanic.loc[titanic.Embarked.isin(['S']), 'Survived'].sum()  # 1이 생존자이므로


#미혼 여성중에 나이값이 모르는 사람의 수
# contains 정규 표현식 사용가능
titanic.loc[titanic.Name.str.contains("Miss."),'Age'].isnull().sum()

 

boolean mask 주의 사항

주의) boolean mask는 무조건 mask와 적용 대상의 index가 같아야 한다.

 

5. Pivot Table

 

# 성별을 기준으로 생존률 파악 --> Mean vs Sum
pd.pivot_table(data=titanic, index='Sex', values='Survived', aggfunc='mean')

# 	Survived
# Sex	
# female	0.742038
# male	0.188908

 

# 사회 계급을 기준으로 생존률 파악
pd.pivot_table(data=titanic, index='Pclass', values='Survived', aggfunc=['count', 'sum', 'mean'])


# 	count	sum	mean
# Survived	Survived	Survived
# Pclass			
# 1	216	136	0.629630
# 2	184	87	0.472826
# 3	491	119	0.242363


pd.pivot_table(data=titanic, index=['Sex', 'Pclass'], values='Survived', aggfunc=['count', 'sum', 'mean'])

# 		count	sum	mean
# Survived	Survived	Survived
# Sex	Pclass			
# female	1	94	91	0.968085
# 			2	76	70	0.921053
# 			3	144	72	0.500000
# male		1	122	45	0.368852
#		 	2	108	17	0.157407
# 			3	347	47	0.135447

 

 

▣ Seaborn

Library import

import seaborn as sns
sns.__version__
from seaborn import load_dataset

data = load_dataset('penguins').dropna() # NaN이 하나라도 포함된 row가 있다면 제외.

 

Histplot

  • 가장 기본적으로 사용되는 히스토그램을 출력하는 plot.
  • 전체 데이터를 특정 구간별 정보를 확인할 때 사용.
  • 수치형 데이터(연속형)의 대략적인 분포를 확인할 때 사용.
  • 구간별 count를 계산해서 막대로 표현하는 그래프.
# penguin 데이터에 histplot을 출력합니다.
import matplotlib.pyplot as plt

# seaborn 그래프 그리기 전 - seaborn이 덮어 그릴 수 있기 때문에 주의
plt.figure(figsize=(8,5))
plt.title("Bill Length", fontsize=16, loc='left')

sns.histplot(data=data, x='bill_length_mm', bins=30, hue='species', multiple='stack', palette='Spectral')
# 펭귄 부리 길이 bill_length_mm
# bins : 구간의 갯수 = 막대 갯수
# hue : 특성기준별로 색상 달리함
# multiple : 막대그래프 합치기
# pallete :  Blues(단일색상), Set2(discrete-서로 색상이 비교적 다른 편),viridis, Spectral(continuous-연속적으로 색상표현)
# => 김용담강사 자주사용 color palette name들

# seaborn 그래프 그리기고 난 후
plt.xlabel('Bill Length')
plt.ylabel('Number of Penguins')
# plt.xlim(45, 60) # x축 제한
# plt.xticks([n*10 for n in range(7)])
plt.show()

 

distplot

# penguin 데이터에 displot을 출력. kde:밀도(겹치는 걸 볼 때 잘보임), ecdf:누적확률분포
sns.displot(data=data, x="flipper_length_mm", kind='hist', col='island', row='sex', hue='species', multiple='stack')

 

barplot

sns.barplot(data=data, x='species', y='body_mass_g', errorbar=None)
# 기본 body_mass_g 평균, errorbar=None : 오차 표시 없애기

plt.figure(figsize=(12,4))
# x, y 값을 바꾸면 가로방향 막대 그래프 표시됨
sns.barplot(data=data, x='body_mass_g', y='species', errorbar=None, hue='species', palette='Set2')

countplot

sns.countplot(data=data,x='species') # 연속형 변수에는 사용하지 않는다.

boxplot

sns.boxplot(data=data, x='species', y='body_mass_g')

# Inter-Quartile-Range
# IQR = q75 - q25
# lower_bound = q25 - IQR * 1.5
# upper_bound = q75 + IQR*1.5

scatterplot

sns.scatterplot(data=data, x='bill_length_mm', y='flipper_length_mm', hue='species')

 

 

 

 

 

heatmap

corr = data.corr(numeric_only=True)  # str 에러 날 때 numeric_only=True

sns.heatmap(data=corr, annot=True, fmt='.4f', cmap='Blues')  # annot=True 숫자 표시