Hard skills/Python (32)


Python 중고급 - map, filter, reduce 


파이썬의 기초를 익힌 후, 파이썬의 중고급 문법을 선택적으로 배운다면 기본 문법으로도 구현할 수 있는 로직들을 더욱 쉽고, 간편하게 구현해볼 수 있습니다. 이번 포스팅에서는 list를 다루는 함수인 map, filter, reduce에 대해 간단하게 정리해보겠습니다. 물론 map, filter, reduce를 안 쓰고 코딩하는 것도 가능하며, 그것이 편하시면 그렇게 하셔도 좋습니다. 하지만 이를 사용하는 프로그래머나 데이터 분석가들이 꽤 있기 때문에 이러한 문법들을 알아두면 기존의 코드를 이해하는데 큰 도움이 될 수 있을 것입니다. 


Map


map의 경우, list의 element에 함수를 적용시켜 결과를 반환하고 싶을 때 사용합니다. 만약, 어떤 리스트의 원소를 제곱한 새로운 리스트를 만들고 싶다면, 일반적인 C언어 스타일의 해결법은 아래와 같습니다. 


# Map function
# 문제와 일반적인 해결법
items = [1, 2, 3, 4, 5]
squared = []
for i in items:
    squared.append(i**2)

하지만 map function을 통해 짧게 구현할 수 있습니다. map함수의 구조는 map(function_to_apply, list_of_inputs) 입니다.


items = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, items))

위 코드는 items 리스트의 element 각각에 대해 제곱을 해서 새로운 리스트를 만들어 반환합니다. filter와 reduce도 이와 같은 구조를 갖습니다. 


이 때, map 앞에 list 함수를 통해 list 자료형으로 바꾸는 이유는 map 이 반환하는 것이 실제로는 list 자료형이 아니기 때문입니다. map 함수는 Iterator 를 반환하는데, 이를 list로 변환해서 list 자료형으로 만드는 것입니다. Iterator는 next() 함수를 갖는 파이썬 객체로 꼭 메모리에 올릴 데이터만 올려서 메모리를 효율적으로 이용할 수 있는 파이썬의 대표적인 객체입니다. 바로 list로 반환하는 것이 아니라 Iterator로 보다 상위의 객체를 리턴하는 것은, 다른 map 함수의 리턴값을 리스트가 아닌 다른 자료구조로 변환시킬 수도 있도록 하기 위해서입니다. 예를 들어, set 자료구조로도 변환시킬 수 있습니다. 


map_it = map(lambda x: x**2, items)
next(map_it)

map 함수의 결과는 Iterator 이므로, next 함수를 위와 같이 실행할 수 있습니다. 위 코드의 결과는 1입니다.


Iterator 에 대해서는 다음에 다루어 보도록 하겠습니다. 



Filter


Filter의 경우, list의 element에서 어떤 함수의 조건에 일치하는 값만 반환하고 싶을 때 사용합니다. 


As the name suggests, filter creates a list of elements for which a function returns true.


만약 어떤 리스트에서 '음수만 골라내고 싶다' 라고 할 때, filter 함수를 사용한 코딩 방법은 아래와 같습니다. 


number_list = range(-5, 5)
less_than_zero = list(filter(lambda x: x < 0, number_list))
print(less_than_zero)

이 때, less_than_zero 는 -5, -4, -3, -2, -1 을 갖습니다. 


만약 Map과 Filter를 보고 저거 list comprehension 으로 할 수 있는 거 아니야? 라고 생각하실 수 있습니다. 맞습니다. Map과 Filter가 자신과 맞지 않다고 생각하는 경우 list comprehension 만으로도 위 코드들을 훨씬 더 간결하게 구현할 수 있습니다.


# Map을 list comprehension으로 구현 items = [1, 2, 3, 4, 5] squared = list(map(lambda x: x**2, items)) print(squared)   squared = [x**2 for x in items] print(squared)   # Filter를 list comprehension으로 구현 number_list = range(-5, 5) less_than_zero = list(filter(lambda x: x < 0, number_list)) print(less_than_zero)   less_than_zero = [x for x in number_list if x <0] print(less_than_zero)  

위 코드처럼 list comprehension 만을 통해 map, filter가 하는 것을 할 수 있습니다. 


Filter가 마음에 들지 않는 경우, list comprehension을 쓸 수도 있습니다 하지만, map, filter가 있다는 것을 알아 두면 좋습니다.


Reduce


reduce는 어떤 list에 대해서 computation을 해서 결과를 내보낼 때, 즉 결과를 어떤 함수로 computation해서 결과를 축약하기 위해서 사용됩니다. 이 때, reduce함수에 input으로 들어가는 함수는 두 element를 연산하는 로직을 넣어야 합니다. 이것은 list comprehension으로 하지 못하는 것입니다. 


아래 코드는 reduce함수를 이용하는 것을 포함하여 파이썬으로 1-100 까지의 합을 구하는 총 3가지 방법입니다. 


# 1-100 까지의 합을 구하라   # C 언어 스타일의 해법 sum_value = 0 for i in range(1, 101) : sum_value += i print(sum_value)   # python 스러운 해법 sum_value = reduce((lambda x,y : x+y), [x for x in range(1,101)]) print(sum_value)   # 하지만 Data scientist가 가장 먼저 생각해는 해법 import numpy as np print(np.sum([x for x in range(1,101)])


참고 


Intermediate python (http://book.pythontips.com/en/latest/)

  • list(map(....에 대하여 2019.02.19 15:14

    좋은 글 잘 봤습니다. 덕분에 궁금했던 부분을 해결하고 혼자 예제로 실습해봤습니다.

    다만 작성하신 부분 중 이 부분은 python3에만 해당되는건가요?

    type(list(map(lambda x: x**2, items)))과 type(map(lambda x: x**2, items))

    모두 list로 판단되는데요. (ipython으로 test해본 결과 next(map(...))이 작동 안됨)

    혹시나 몰라서 ipython3에서는 말씀하신 대로 작동하는 것 같고요.

    변경된건가요?

    • Deepplay 2019.02.20 01:20 신고

      안녕하세요. Iterator의 다음 원소를 출력할 때 python2.x 버전에서는 .next() 을 사용했는데, python3에서는 next() 로 변경되었습니다. https://portingguide.readthedocs.io/en/latest/iterators.html 참고하시라고 링크 남깁니다. ^^


주피터 nbextension 설치


우선 가상환경을 쓰시는 분들은 가상환경을 activate하시기 바랍니다.


source activate [가상환경이름]


pip install jupyter_nbextensions_configurator jupyter_contrib_nbextensions

jupyter contrib nbextension install --user

jupyter nbextensions_configurator enable --user


위 커맨드를 입력하시면 nbextension이 설치됩니다. 원래는 Clusters 옆에 nbextension 탭이 생기게 됩니다.



저의 경우에는 왜 인지는 모르겠지만 자동으로 nbextension 탭이 안 생겼는데

[아이피]:[포트]/nbextensions 로 들어가니까 아래처럼 nbextension 메뉴를 볼 수 있었습니다. 


메뉴에서 사용 가능한 extension 들이 나와있고, 클릭을 통해 enable/disable을 할 수 있습니다.

처음 사용하실 때는 각각의 모듈들을 설치하셔야 합니다.


주피터 테마 설치


주피터 테마

https://github.com/dunovank/jupyter-themes


마찬가지로 가상환경 activate 후에


pip install jupyterthemes


커맨드 옵션은 아래와 같습니다.


jt  [-h] [-l] [-t THEME] [-f MONOFONT] [-fs MONOSIZE] [-nf NBFONT]

    [-nfs NBFONTSIZE] [-tf TCFONT] [-tfs TCFONTSIZE] [-dfs DFFONTSIZE]

    [-m MARGINS] [-cursw CURSORWIDTH] [-cursc CURSORCOLOR] [-vim]

    [-cellw CELLWIDTH] [-lineh LINEHEIGHT] [-altp] [-altmd] [-altout]

    [-P] [-T] [-N] [-r] [-dfonts]


jt -l 로 적용가능한 테마들을 볼 수 있습니다.


저의 경우에는 onedork 테마가 마음에 들어서


jt -t onedork -fs 95 -tfs 11 -nfs 115 -cellw 70% -T

이러한 옵션으로 사용중인데, 폰트 사이즈도 적절하고 화면이 주피터 노트북 default보다 조금 넓은 정도여서 편리하게 사용하고 있습니다.


각각의 옵션이 무엇을 뜻하는지에 대한 설명은

https://github.com/dunovank/jupyter-themes 을 참조하시면 됩니다.


또 테마를 설치한 후, output화면에서 왼쪽이 약간 잘려 나오는 문제가 있었는데

~/.jupyter/custom/custom.css 에서

div.output_area {
display: -webkit-box;
padding-left: 20px;
}

를 추가하여 해결하였습니다. 위 파일을 수정하면 jupyter notebook의 css 설정을 직접 바꾸어 자신만의 테마를 만들어 볼 수도 있습니다.


Python으로 하는 탐색적 자료 분석 (Exploratory Data Analysis)


Python을 통해 탐색적 자료분석을 할 때, 무엇을 해야하고, 순서는 어떻게 해야하는지 막막한 경우가 많은데요. 탐색적 자료분석의 기본은 바로 변수 별로 분포를 그려보는 것이겠죠. 수치형 데이터의 경우는 히스토그램을, 명목형 데이터의 경우는 빈도표를 통해 데이터의 분포를 살펴보게 됩니다. 본 포스팅에서는 파이썬을 통해 탐색적 자료 분석을 하는 방법을 유명한 데이터셋인 타이타닉 데이터를 통하여 차근차근 알아보겠습니다. 


titanic.csv



기본적인 탐색적 자료 분석의 순서는 아래와 같이 정리해보았습니다. 


1. 데이터를 임포트하여 메모리에 올린다.

2. 데이터의 모양을 확인 한다.

3. 데이터의 타입을 확인한다.

4. 데이터의 Null 값을 체크한다. 

5. 종속변수의 분포를 살펴본다.

6. 독립변수 - 명목형 변수의 분포를 살펴본다. 

7. 독립변수 - 수치형 변수의 분포를 살펴본다. 

8. 수치형, 명목형 변수간의 관계를 파악한다. 


1. 데이터를 임포트한다.


아래와 같이 패키지와 데이터를 임포트합니다. numpy, pandas, matplotlib, seaborn은 이 4가지의 패키지는 파이썬을 통한 EDA에서 거의 필수적으로 사용하는 라이브러리입니다.


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
 
titanic = pd.read_csv("titanic.csv")


2. 데이터의 모양을 확인한다. 


titanic.head()

PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
0103Braund, Mr. Owen Harrismale22.010A/5 211717.2500NaNS
1211Cumings, Mrs. John Bradley (Florence Briggs Th...female38.010PC 1759971.2833C85C
2313Heikkinen, Miss. Lainafemale26.000STON/O2. 31012827.9250NaNS
3411Futrelle, Mrs. Jacques Heath (Lily May Peel)female35.01011380353.1000C123S
4503Allen, Mr. William Henrymale35.0003734508.0500NaNS



3. 데이터의 타입을 체크한다.


titanic.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            891 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Cabin          204 non-null object
Embarked       889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB


데이터의 타입을 체크하는 이유는 해당 변수의 타입을 제대로 맞추어주기 위해서입니다. 범주형 변수의 경우 object 또는 string, 수치형 변수의 경우 int64 혹은 float 64로 맞추어주면 됩니다. 범주형 변수의 경우 값이 문자열로 들어가 있으면 알아서 object 타입이 되지만, 만약의 숫자로된 범주형 변수의 경우 int64 등으로 잘못 타입이 들어가 있는 경우가 있습니다.  


위 데이터의 경우 Survived와 PClass 변수가 범주형 int64로 잘못 되어있으므로 형변환을 합니다.


titanic['Survived'] = titanic['Survived'].astype(object)
titanic['Pclass'] = titanic['Pclass'].astype(object)


4. 데이터의 Null 값을 체크한다. 


Null Check도 매우 중요한 작업 중 하나입니다. 단순히 Tutorial이나 학습을 위해 제작된 데이터셋이아닌 현실의 데이터셋의 경우, 많은 부분이 Null 인 경우가 많습니다. 따라서 이 Null 값을 어떻게 처리하느냐가 매우 중요합니다. 


titanic.isnull().sum()

PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age              0
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64



Cabin 변수가  687 행이 missing이고 Embarked가 2개의 행이 missing인 것을 확인하였습니다.


Null 값이 있는 경우, 크게 그 값을 빼고 하는지, 혹은 결측치를 대치하는지 2개의 방법으로 나눌 수 있습니다. 각각의 방법에 대한 이름이 다르긴한데 보통 첫 번째 방법을 complete data analysis, 두 번째 방법을 Imputation이라고 이름 붙입니다. 


missing_df = titanic.isnull().sum().reset_index()
missing_df.columns = ['column', 'count']
missing_df['ratio'] = missing_df['count'] / titanic.shape[0]
missing_df.loc[missing_df['ratio'] != 0]

columncountratio
10Cabin6870.771044
11Embarked20.002245


위 명령어를 통해 전체의 몇 %가 missing 인지를 확인할 수 있습니다. 




5. 종속변수 체크


titanic['Survived'].value_counts().plot(kind='bar') plt.show()


기본적으로 종속변수의 분포를 살펴봅니다. 종속변수란 다른 변수들의 관계를 주로 추론하고, 최종적으로는 예측하고자 하는 변수입니다. 



6. 명목형 변수의 분포 살펴보기

 


단변수 탐색


category_feature = [ col for col in titanic.columns if titanic[col].dtypes == "object"]
category_feature
['Survived', 'Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked']

앞에서 명목형 변수의 형을 object로 모두 변경했기 때문에 이처럼 컬럼 중에서 object 타입을 가진 컬럼만 뽑아서 명목형 변수의 리스트를 만듭니다. 이 때, 데이터의 기본키(인덱스), 종속변수 등을 제외하고 분석하는 것이 좋습니다. 



category_feature = list(set(category_feature) - set(['PassengerId','Survived']))
category_feature
  ['Cabin', 'Embarked', 'Ticket', 'Sex', 'Name', 'Pclass']



다음으로는 그래프를 통해 명목형 변수의 분포를 살펴보는 것입니다. 


for col in categorical_feature: titanic[col].value_counts().plot(kind='bar') plt.title(col) plt.show()




이렇게 살펴봄으로써 명목형 변수를 어떻게 다룰지를 판단할 수 있습니다. 예를 들어, 카테고리수가 너무 많고, 종속변수와 별로 관련이 없어보이는 독립 변수들은 빼고 분석하는 것이 나을 수도 있습니다.



이변수 탐색



sex_df = titanic.groupby(['Sex','Survived'])['Survived'].count().unstack('Survived')
sex_df.plot(kind='bar', figsize=(20,10))
plt.title('Sex')
plt.show()


성별-생존의 관계 파악처럼 두 변수의 관계를 파악하기 위해서는 위와 같이 확인할 수 있습니다.


7. 수치형 변수의 분포 살펴보기


단변수 탐색


단변수 탐색은 seaborn 패키지의 distplot 함수를 이용하면 매우 편합니다.


우선 이와 같이 전체 변수 중에서 범주형 변수와 기타 인덱스 변수, 종속변수들을 제외하고 수치형 변수만 골라냅니다.

numerical_feature = list(set(titanic.columns) - set(category_feature) - set(['PassengerId','Survived']))
numerical_feature = np.sort(numerical_feature)
numerical_feature

변수별로 for문을 돌면서 distplot을 그립니다


for col in numerical_feature:
    sns.distplot(titanic.loc[titanic[col].notnull(), col])
    plt.title(col)
    plt.show()


이변수, 삼변수 탐색


seaborn 패키지의 pairplot을 통해 종속변수를 포함한 3개의 변수를 한 번에 볼 수 있도록 플로팅합니다.


sns.pairplot(titanic[list(numerical_feature) + ['Survived']], hue='Survived', 
             x_vars=numerical_feature, y_vars=numerical_feature)
plt.show()



pairplot은 어러 변수의 관계를 한 번에 파악할 수 있으며,  hue 파라미터를 통해 종속변수를 지정함으로써 세 변수의 관계를 파악할 수 있습니다.



8. 수치형, 명목형 변수 간의 관계 탐색


앞서서 수치형-수치형  간의 관계, 그리고 명목형-명목형 간의 관계에 종속변수까지 포함해서 보았습니다. 이 번에는 수치형-명목형 간의 관계를 파악해 보는 것입니다. 예를 들어, 성별, 나이, 생존여부 3개의 변수를 동시에 탐색하고 싶을 수 있습니다. 이 경우에 명목형 변수에 따라 수치형변수의 boxplot을 그려봄으로써 대략적인 데이터의 형태를 살펴볼 수 있습니다. 


unique_list = titanic['Sex'].unique()
 
for col in numerical_feature:
    plt.figure(figsize=(12,6))
    sns.boxplot(x='Sex', y=col, hue='Survived', data=titanic.dropna())
    plt.title("Sex - {}".format(col))
    plt.show()


  • 조성훈 2018.12.03 11:43

    6번 단변수 탐색에서 merged_processed가 정의가 되어 있지 않은 것 같은데요..

    • Deepplay 2018.12.03 13:54 신고

      지적 감사합니다. 수정하였습니다.

  • 머신러닝 잘하고 싶다... 2019.07.28 11:44

    "3. 데이터의 타입을 체크한다" 에서 survivied하고 PClass의 변수가 명목형인 이유는 무엇입니까??

    • Deepplay 2019.07.29 16:23 신고

      안녕하세요. Pclass의 경우 명목형으로 보기 힘들 수도 있을것 같네요. 범주형으로 수정하였습니다. 감사합니다.

  • bsi1209 2020.05.18 15:54

    파이썬 입문자입니다. 많은 도움 되었습니다. 감사합니다.

  • 감사합니다 2020.06.16 20:45

    깔끔한 정리 감사합니다

  • 초심자 2020.08.21 11:22

    제가 입문자라서 잘 모르는데 혹시 7. 수치형 변수의 분포 살펴보기- 단변수 탐색에서
    np.sort는 왜 사용되었는지 알 수 있을까요?

Pandas (Python Data Analysis Library)



앞선 포스팅에 이어 Pandas 중고급 문법 튜토리얼입니다. 본 포스팅은 해당 포스팅을 참고하였습니다.


5. 컬럼명, 인덱스명 수정하기


df = pd.DataFrame(data=np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), columns=['A', 'B', 'C'])
display(df)

# Define the new names of your columns
newcols = {
    'A': 'new_column_1', 
    'B': 'new_column_2', 
    'C': 'new_column_3'
}

# Use `rename()` to rename your columns
df.rename(columns=newcols, inplace=True)

# Rename your index
df.rename(index={1: 'a'})



이번엔 기본기를 넘어 pandas의 고급 문법들을 살펴 보자.



6. 데이터 Formatting


Replace


dataframe의 특정 문자열을 바꾸고 싶을 때는 replace를 이용한다.


df = pd.DataFrame(data=np.array([["Awful", "Poor", "OK"], ["OK", "Acceptable", "Perfect"], ["Poor", "Poor", "OK"]]), columns=['A', 'B', 'C'])
display(df)

display(df.replace("Awful", "Nice"))

# Replace the strings by numerical values (0-4)
display(df.replace(['Awful', 'Poor', 'OK', 'Acceptable', 'Perfect'], [0, 1, 2, 3, 4]))



String의 일부 제거


map function과 lambda를 이용해 특정 컬럼의 모든 데이터에 함수를 적용시킬 수 있다. 


df = pd.DataFrame(data=np.array([[1, 2, "+6a"], [4, 5, "+3b"], [5, 5, "+2C"]]), columns=['A', 'B', 'result'])

# Check out your DataFrame
display(df)

# Delete unwanted parts from the strings in the `result` column
df['result'] = df['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))

# Check out the result again
display(df)



7. 열 또는 행에 함수 적용하기


map 또는 apply 함수를 이용한다.

df = pd.DataFrame(data=np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), columns=['A', 'B', 'C'])
doubler = lambda x: x*2
print(df.head())
print(df['A'].map(doubler))
print(df.apply(doubler))
print(df.iloc[1].apply(doubler))
  A  B  C
0  1  2  3
1  4  5  6
2  7  8  9
0     2
1     8
2    14
Name: A, dtype: int64
    A   B   C
0   2   4   6
1   8  10  12
2  14  16  18
A     8
B    10
C    12
Name: 1, dtype: int64



8. 빈 데이터 프레임 만들기 (Creating empty dataframe)


가끔 빈 데이터 프레임을 만들고, 그 곳에 값을 채워야할 경우가 있다. 이것을 하기 위해서는 인덱스와 컬럼을 만든 후, np.nan 을 채워주면 된다.

df = pd.DataFrame(np.nan, index=[0,1,2,3], columns=['A'])
print(df)

그 속에 값을 채워넣고 싶으면 아래처럼 할 수 있다.

df.loc[1, 'A'] = "A"


9. 데이터 임포트시, 날짜, 시간 parsing 하기


만약 날짜 시간으로된 컬럼을 datatime 으로 parsing 하기 위해서는 read_csv 메소드의 parse_dates 를 이용할 수 있다.

import pandas as pd
dateparser = lambda x: pd.datetime.strptime(x, '%Y-%m-%d %H:%M:%S')

# Which makes your read command:
pd.read_csv(infile, parse_dates=['columnName'], date_parser=dateparse)

# Or combine two columns into a single DateTime column
pd.read_csv(infile, parse_dates={'datetime': ['date', 'time']}, date_parser=dateparse)  


10. 데이터프레임 재구성하기


pivot 함수를 통해 index, column, values 를 지정하여 재구성할 수 있다.

import pandas as pd

products = pd.DataFrame({'category': ['Cleaning', 'Cleaning', 'Entertainment', 'Entertainment', 'Tech', 'Tech'],
        'store': ['Walmart', 'Dia', 'Walmart', 'Fnac', 'Dia','Walmart'],
        'price':[11.42, 23.50, 19.99, 15.95, 55.75, 111.55],
        'testscore': [4, 3, 5, 7, 5, 8]})

print(products)

pivot_products = products.pivot(index='category', columns='store', values='price')
print(pivot_products)
 category    store   price  testscore
0       Cleaning  Walmart   11.42          4
1       Cleaning      Dia   23.50          3
2  Entertainment  Walmart   19.99          5
3  Entertainment     Fnac   15.95          7
4           Tech      Dia   55.75          5
5           Tech  Walmart  111.55          8
store            Dia   Fnac  Walmart
category                            
Cleaning       23.50    NaN    11.42
Entertainment    NaN  15.95    19.99
Tech           55.75    NaN   111.55


Stack & Unstack


pivot 함수를 이용할 수도 있지만, stack 과 unstack 함수를 이용하여 pivoting 을 할 수 있다. stack 은 column index를 raw index로 바꾸어 데이터프레임이 길어지고, unstack 은 raw index를 column index로 바꾸어 데이터프레임이 넓어진다. 


Stack 과 Unstack 의 개념도


본 포스팅에서는 간단한 개념만 설명하며, 테이블 pivoting 관련해서는 이 페이지 (https://nikgrozev.com/2015/07/01/reshaping-in-pandas-pivot-pivot-table-stack-and-unstack-explained-with-pictures/) 가 정리가 잘 되어있으니 참고하시면 좋을 것 같다.


melt 함수


melt 함수는 column 을 row로 바꾸어준다. 녹으면 흘러내리니까 column 이 raw로 흘러려 column 이 짧아지고 raw 는 길어진다고 이해하면 쉽다. 

# The `people` DataFrame
people = pd.DataFrame({'FirstName' : ['John', 'Jane'],
                       'LastName' : ['Doe', 'Austen'],
                       'BloodType' : ['A-', 'B+'],
                       'Weight' : [90, 64]})

print(people)

# Use `melt()` on the `people` DataFrame
print(pd.melt(people, id_vars=['FirstName', 'LastName'], var_name='measurements'))
FirstName LastName BloodType  Weight
0      John      Doe        A-      90
1      Jane   Austen        B+      64
  FirstName LastName measurements value
0      John      Doe    BloodType    A-
1      Jane   Austen    BloodType    B+
2      John      Doe       Weight    90
3      Jane   Austen       Weight    64



11. 데이터 프레임 반복하기


iterrows 함수를 이용하여 iteration 할 수 있다. 매우 유용하다.

df = pd.DataFrame(data=np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), columns=['A', 'B', 'C'])

for index, row in df.iterrows() :
    print(row['A'], row['B'])
1 2
4 5
7 8


Pandas (Python Data Analysis Library)


파이썬을 통해 데이터 분석을 할 때, Pandas를 빼놓고 이야기할 수 없다. 온전히 통계 분석을 위해 고안된 R 과는 다르게 python은 일반적인 프로그래밍 언어(general purpose programming language) 이며, 데이터 분석을 하기 위해서는 여러가지 라이브러리를 사용할 수 밖에 없다. 이 패키지들 중 R의 dataframe 데이터 타입을 참고하여 만든 것이 바로 pandas dataframe이다. pandas는 dataframe을 주로 다루기 위한 라이브러리이며, dataframe을 자유롭게 가공하는 것은 데이터 과학자들에게 중요하다. 물론 pandas의 문법을 외우지 않고, 필요할 때마다 책이나 웹에서 찾아가면서 해도 좋지만 자주 사용하는 조작법을 외운다면 안 그래도 귀찮은 데이터 핸들링 작업을 빠르게 할 수 있다.


그래서 본 포스팅에서는 pandas dataframe을 다루는 법을 간단하게 정리해보고자 한다. 

본 포스팅은 이 튜토리얼을 참고하였다. 



Tabular Data type


Pandas Dataframe은 테이블 형식의 데이터 (tabular, rectangular grid 등으로 불림)를 다룰 때 사용한다. pandas dataframe의 3요소는 컬럼, 데이터(로우), 인덱스가 있다. 그리고 파이썬의 기본 데이터 타입으로 list, tuple, dictionary가 있다는 것도 다시 한 번 떠올리고 바로 튜토리얼을 해보자. 본 튜토리얼은 jupyter notebook, python3 환경에서 작성되었다.


1. Pandas Dataframe 만들기


pandas dataframe은 다양한 데이터 타입으로부터 만들 수 있다. ndarray, dictionary, dataframe, series, list의 예를 들고 있다.(IPython의 display 함수는 IPython 쉘 환경에서 pandas dataframe을 테이블 형식으로 표현해준다.)


# 1. Create Pandas Dataframe
from IPython.display import display
# Take a 2D array as input to your DataFrame 
my_2darray = np.array([[1, 2, 3], [4, 5, 6]])
display(pd.DataFrame(my_2darray))

# Take a dictionary as input to your DataFrame 
my_dict = {"a": ['1', '3'], "b": ['1', '2'], "c": ['2', '4']}
display(pd.DataFrame(my_dict))

# Take a DataFrame as input to your DataFrame 
my_df = pd.DataFrame(data=[4,5,6,7], index=range(0,4), columns=['A'])
display(pd.DataFrame(my_df))

# Take a Series as input to your DataFrame
my_series = pd.Series({"United Kingdom":"London", "India":"New Delhi", "United States":"Washington", "Belgium":"Brussels"})
display(pd.DataFrame(my_series))


01

1

abc
0112
1324
A
04
15
26
37
BelgiumBrussels
IndiaNew Delhi
United KingdomLondon
United StatesWashington


Series의 경우 pandas에서 제공하는 데이터타입인데, index가 있는 1차원 배열이라고 생각하면 좋다. 문자, 논리형, 숫자 모든 데이터타입이 들어갈 수 있다. dataframe의 한 컬럼, 한 컬럼이 series이다.


Dataframe 간단하게 살펴보기


df.shape를 통해 dataframe의 row와 column 수를 알 수 있다. .index를 통해 index를 알 수 있으며, len을 통해 dataframe의 길이(row의 갯수)를 알 수 있다.


df = pd.DataFrame(np.array([[1, 2, 3], [4, 5, 6]]))

# Use the `shape` property
print(df.shape)

# Or use the `len()` function with the `index` property
print(len(df.index))


(2, 3)
2


list(df.columns)


[0, 1, 2]



2. Dataframe에서 특정 컬럼이나 로우(인덱스) 선택하기


실무적으로 가장 많이 사용되는 부분중 하나는 바로 Dataframe에서 특정 컬럼이나 로우를 선택하여 그 값을 보는 것이다. pandas에서는 상당히 다양한 방법들이 있다. 대표적으로 iloc, loc, ix 등을 통해서 할 수 있다. 필자는 column을 조회할때 df['column'], row를 조회할 때는 df.ix[index]를 많이 사용한다. 가장 짧고 기억하기 쉽다.


우선 파이썬 기본 데이터 타입인 dictionary 데이터 타입을 통해 dataframe을 만든다. 


df = pd.DataFrame({"A":[1,4,7], "B":[2,5,8], "C":[3,6,9]})


# Use `iloc[]` to select a row
display(df.iloc[0])
display(df.loc[0])
display(df.ix[0])

# Use `loc[]` to select a column
display(df.loc[:,'A'])
display(df['A'])

# 특정 row, column을 선택하기
display(df.ix[0]['A'])
display(df.loc[0]['B'])


A    1
B    2
C    3
Name: 0, dtype: int64

A 1 B 2 C 3 Name: 0, dtype: int64

A 1 B 2 C 3 Name: 0, dtype: int64

0 1 1 4 2 7 Name: A, dtype: int64

0 1 1 4 2 7 Name: A, dtype: int64

1

2


3. Dataframe에 컬럼, 로우, 인덱스 추가하기


2에서는 Dataframe의 특정 컬럼, 로우에 접근하는 방법을 알아보았고, 이제 데이터를 추가하거나 수정, 삭제하는 실제 작업 방법을 알아보자.


인덱스 설정하기


pandas는 기본적으로 row에 인덱스를 0부터 차례대로 자연수를 부여한다. 이를 변경하는 방법은 set_index 함수를 이용하는 것이다. 아래의 df.set_index('A')는 A 컬럼을 인덱스로 지정하는 것을 뜻한다. 그러면 3개의 row에 대하여 인덱스가 1,4,7이 부여된다. 


# Print out your DataFrame `df` to check it out
df = pd.DataFrame({"A":[1,4,7], "B":[2,5,8], "C":[3,6,9]})
display(df)

# Set 'C' as the index of your DataFrame
df = df.set_index('A')
display(df)


 

A

B
 0 1 2 3
 1 4 5 6
 2 7 8 9
BC
A
123
456
789

인덱스 접근하기 (ix와 iloc의 차이점)

인덱스를 접근하는 방법에 여러가지가 있다. ix, loc, iloc 등을 쓸 수 있다. 필자는 ix를 주로 쓴다. ix와 iloc의 차이점은 iloc은 인덱스와 상관 없이 순서를 보고 row를 불러온다. 아래 코드에서 ix[7]은 index가 7인 로우를 불러오는 것이며, iloc[1]은 1번째 로우를 불러오는 것이다. (0번째 로우부터 시작) 근데 ix의 경우 주의할점은 index가 integer로만 이루어진 경우에는 index로 접근하지만, index에 문자가 껴있으면 순서로 접근한다는 점이다.  

print(df.ix[7])
print(df.iloc[1])

B    8
C    9
Name: 7, dtype: int64
B    5
C    6
Name: 4, dtype: int64


예를 들어, 아래 코드를 보면, 인덱스를 문자와 숫자가 혼합된 형태로 주었다. 이 때 ix[2]로 로우를 부르면 인덱스가 아니라 순서로 로우를 불러오게된다. 


df = pd.DataFrame(data=np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), index= [2, 'A', 4], columns=[48, 49, 50])

display(df)
# Pass `2` to `loc`
print(df.loc[2])

# Pass `2` to `iloc`
print(df.iloc[2])

# Pass `2` to `ix`
print(df.ix[2])



48

49

50 

 2

 1 2 3
 A 4 5  6
 4 7 8 9
48    1
49    2
50    3
Name: 2, dtype: int64
48    7
49    8
50    9
Name: 4, dtype: int64
48    7
49    8
50    9
Name: 4, dtype: int64


로우 추가하기


ix의 경우 df.ix[2]를 입력하면 index=2인 곳의 row를 교체한다. 만약 index=2인 row가 없다면 position=2에 row를 추가하게 된다. loc을 사용하면 새로운 index=2인 row를 만들고, 그 곳에 row를 추가하게 된다. 


df = pd.DataFrame(data=np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), index= [2.5, 12.6, 4.8], columns=[48, 49, 50])
display(df)

# There's no index labeled `2`, so you will change the index at position `2`
df.ix[2] = [60, 50, 40]
display(df)

# This will make an index labeled `2` and add the new values
df.loc[2] = [11, 12, 13]
display(df)


Append를 이용해 로우 추가하기


때론 인덱스를 신경쓰지 않고 그냥 데이터의 가장 뒤에 row를 추가하고 싶을 수도 있다. 이 경우에는 append를 사용하면 좋다. 아래는 df 데이터프레임에 a를 추가하여 row를 추가하는 코드를 보여준다. row를 추가한 후에 reset index를 통해 index를 0부터 새롭게 지정한다. 이는 실제 작업할 때 많이 쓰는 테크닉이다. 


df = pd.DataFrame(data=np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), columns=[48, 49, 50])
display(df)

a = pd.DataFrame(data=[[1,2,3]], columns=[48,49,50])
display(a)

df = df.append(a)
df = df.reset_index(drop=True)
display(df)




48 

4950
 0  1 2 3
 1 4 5 6
 2 7 8 9
484950
0123



48 4950
 0  1 2 3
 1 4 5 6
 2 7 8 9
 3 1 2 3



컬럼 추가하기


컬럼을 추가하는 방법도 여러가지가 있다. 아래는 loc을 통해 추가하거나, df['column']을 통해 추가하는 방법이다.


df = pd.DataFrame(data=np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), columns=['A', 'B', 'C'])

# Study the DataFrame `df`
display(df)

# Append a column to `df`
df.loc[:, 'D'] = pd.Series(['5', '6', '7'], index=df.index)

# Print out `df` again to see the changes
display(df)

df['E'] = pd.Series(['5', '6', '7'], index=df.index)
display(df)



 A BC
 0 1 2 3
 1 4 5 6
 2 7 8

ABCD
01235
14566
27897

ABCDE
012355
145666
278977



4. Dataframe의 인덱스, 컬럼, 데이터 삭제하기


인덱스 삭제


인덱스를 지워야할 경우는 그렇게 많지 않을 것이다. 주로 reset_index를 이용해서 index를 리셋하는 것을 많이 사용한다. 혹은 index의 이름을 삭제하고 싶다면 del df.index.name을 통해 인덱스의 이름을 삭제할 수 있다. 


컬럼 삭제


drop 명령어를 통해 컬럼 전체를 삭제할 수 있다. axis=1은 컬럼을 뜻한다. axis=0인 경우, 로우를 삭제하며 이것이 디폴트이다. inplace의 경우 drop한 후의 데이터프레임으로 기존 데이터프레임을 대체하겠다는 뜻이다. 즉, 아래의 inplace=True는 df = df.drop('A', axis=1)과 같다.


df = pd.DataFrame(data=np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), columns=['A', 'B', 'C'])
display(df)

# Drop the column with label 'A'              
# drop axis의 경우 column이면 1, row이면 0이다.
df.drop('A', axis=1, inplace=True)
display(df)




A

B

 0 1 2
 1 4

6

 2 7 8
BC
023
156
289


로우 삭제


- 중복 로우 삭제


drop_duplicate를 사용하면 특정 컬럼의 값이 중복된 로우를 제거할 수 있다. keep 키워드를 통해 중복된 것들 중 어떤 걸 킵할지 정할 수 있다.


df = pd.DataFrame(data=np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [40, 50, 60], [23, 35, 37]]), 
                  index= [2.5, 12.6, 4.8, 4.8, 2.5], 
                  columns=[48, 49, 50])

display(df)

df = df.reset_index()
display(df)

df = df.drop_duplicates(subset='index', keep='last').set_index('index')
display(df)




48

49 50
 2.5 1 2 3

12.6 

 5 6
 4.8 7

8

 4.840 50 60 
 2.523 35 37 

index484950
02.5123
112.6456
24.8789
34.8405060
42.5233537

484950
index
12.6456
4.8405060
2.5233537


- 인덱스를 통한 로우 삭제


drop 명령어를 통해 특정 index를 가진 row를 삭제할 수 있다. df.index[1] 명령어는 1번 째 위치에 있는 index를 가져온다. 가져온 이 index를 drop에 인풋으로 넣어주면 해당 index를 가진 row를 삭제할 수 있다.


# Check out your DataFrame `df`
df = pd.DataFrame(data=np.array([[1, 2, 3], [1, 5, 6], [7, 8, 9]]), columns=['A', 'B', 'C'])
display(df)

# Drop the index at position 1
print(df.index[1])
print(df.drop(df.index[1]))
print(df.drop(0))



A

 B

C
 0 1 
 1 1 5 6
 2 7


   A  B  C
0  1  2  3
2  7  8  9
   A  B  C
1  1  5  6
2  7  8  9

데이터 수정하기


특정 컬럼, 로우의 데이터를 수정하고 싶으면 ix 를 이용하면 편하다. 아래 코드는 인덱스=0, 컬럼=A의 데이터를 0으로 수정한다. 


df = pd.DataFrame(data=np.array([[1, 2, 3], [1, 5, 6], [7, 8, 9]]), columns=['A', 'B', 'C'])
display(df)

df.ix[0]['A'] = 0
display(df)




A

 B

C
 0 1 
 1 1 5 6
 2 7
ABC
0023
1156
2789


  • 잉어빵좋아함 2019.06.09 19:43

    숫자를 예문으로만 드니까 가독성이 너무 떨어지네요

    • Deepplay 2019.06.09 21:13 신고

      피드백 감사합니다. 한 번 되돌아보고 개선해보도록 할게요 :)

  • 감사합니다 2019.10.16 16:10

    ix, iloc, loc에 대해서 정말 자세히 설명해주셔서 감사합니다!
    도움이 많이 되었어요!!

  • asdf 2020.01.16 18:11

    감사합니다.! 도움 많이 받고 갑니다~

  • 321e12 2020.04.11 01:18

    판다스를 직접 쓰는사람입장에서, 정말 많이 쓰게되는것만 딱 정리해놓으셧네요!

  • xeo0 2020.07.21 17:13

    감사해서 댓글을 남기고 갑니다..

  • . . . 2020.11.06 08:33 신고

    좋은자료 감사합니다. 눈에 쏙들어오네요

  • gmddbs50 2020.11.20 20:11

    도움되는 내용 매우 잘 배우고 가여

  • panda 2020.12.02 13:12

    설명을 잘 되어있어 쉽게 이해가 됩니다. 감사합니다


Python 중고급 - 정규표현식, 클래스



정규 표현식


파이썬에서 정규표현식을 이용하는 것은 re 라이브러리를 이용하면 된다. import re 구문으로 re 라이브러리를 임포트할 수 있다. 


print(all([not re.match("a", "cat"), # cat은 a로 시작하지 않는다.
          re.search("a", "cat"), # cat에 a가 존재하기 때문에 
          not re.search("c", "dog"), # c가 dog에 존재하지 않기 때문에
          3 == len(re.split("[ab]", "carbs")), # a,b로 나누면 'c', 'r', 's' 를 원소로 하는 list가 생성된다.
          "R-D-" == re.sub("[0-9]", "-", "R2D2")])) # sub를 통해 숫자를 -로 대체


객체 지향 프로그래밍


Stack 자료구조를 파이썬의 객체 지향을 이용해서 간단하게 구현하였다. add 함수는 stack에 데이터를 저장하고, pop 함수는 stack의 가장 위에 있는 데이터를 삭제한다. stack은 파이썬의 기본 자료구조인 list를 이용하여 구현하였다. 


class stack : 
    def __init__(self) : 
        "이것은 생성자이다."
        self.list = []
        self.length = 0
        
    def add(self, value) :
        self.list.append(value)
        self.length += 1
    
    def pop(self) : 
        del self.list[self.length-1]
        
    def __repr__(self) : 
        """ 파이썬 프롬프트에서 이 함수를 입력하거나 str()으로 보내주면
            Set 객체를 문자열로 표현해줌 """
        return "Stack : " + str(self.list)


my_stack = stack()

my_stack.add(value=3)
print(my_stack)


Stack : [3]


my_stack.add(value=5)

print(my_stack)


Stack : [3, 5]


my_stack.pop()

print(my_stack)


Stack : [3]



Python “SSL: CERTIFICATE_VERIFY_FAILED” Error 해결법


Keras에서 ResNet의 weight를 다운 받다가 이러한 에러가 났다. 환경은 리눅스, 파이썬 3 버전이다.


base_model = ResNet50(weights='imagenet', pooling=max, include_top = False) 


이 코드를 실행시키다가 에러가 났는데 h5 파일을 다운 받아야하는데, SSL certificate 문제가 있다면서 다운을 받지 못하는 문제이다. 

http://blog.pengyifan.com/how-to-fix-python-ssl-certificate_verify_failed/


위 링크를 보고 해결하였는데, 


첫번째 해결법은 PYTHONHTTPSVERIFY 환경변수를 0으로 설정하는 방법이다.


export PYTHONHTTPSVERIFY=0
python your_script


두 번재 해결법은 대안적인 방법으로 문제가 있는 코드 앞에 아래 코드를 넣는 방법이다. 


import os, ssl
if (not os.environ.get('PYTHONHTTPSVERIFY', '') and
    getattr(ssl, '_create_unverified_context', None)):
    ssl._create_default_https_context = ssl._create_unverified_context




Python - Pandas isin 구문


Python에서 테이블 형식의 데이터를 읽고 처리할 때 가장 많이 쓰이는 pandas 라이브러리에서는 다양한 데이터 처리 기능을 구현하고 있다. 이 중에 isin 구문은 열이 list의 값들을 포함하고 있는 모든 행들을 골라낼 때 주로 쓰인다. 


예를 들어, 아래 예제를 보면


df = DataFrame({'A': [1, 2, 3], 'B': ['a', 'b', 'f']})
df.isin([1, 3, 12, 'a'])


이와 같이 이진값을 반환한다. 

       A      B
0   True   True
1  False  False
2   True  False

이를 그대로 쓰는 경우보다 Dataframe의 컬럼에서 어떤 list의 값을 포함하고 있는것만 걸러낼 때 isin 구문이 유용하다.


이러한 데이터프레임이 있을 때

df = pd.DataFrame({'A': [1, 2, 3], 'B': ['a', 'b', 'f']})

AB
01a
12b
23f


A 컬럼의 값이 [1,3,12]를 포함하는 것만 골라낸다.

df[df['A'].isin([1, 3, 12])]
AB
01a
23f


Python - sklearn LabelEncoder, OnehotEncoder 사용


python에서 범주형 변수를 인코딩 하기 위하여 더미변수를 만들거나, one hot encoding을 합니다. 선형회귀 같은 기본적인 통계 모형에서는 더미변수를 많이 쓰지만, 일반적인 머신러닝/딥러닝 접근법에서는 one hot encoding을 많이합니다. one hot encoding을 하기위해서는 직접 함수를 만들어 할 수도 있지만 패키지를 사용하면 편합니다. 가장 편하다고 생각하는 방법이 바로 sklearn 패키지를 통한 one hot encoding 인데요. 예를 들어 아래와 같은 info라는 이름의 pandas dataframe 이 있을 때, class2를 예측변수 y라고 생각하여 one hot encoding 하는 방법을 알아보겠습니다.



id tissue class class2 x y r
0 mdb001 G CIRC B 535 425 197.0
1 mdb002 G CIRC B 522 280 69.0
2 mdb003 D NORM N NaN NaN NaN
3 mdb004 D NORM N NaN NaN NaN
4 mdb005 F CIRC B 477 133 30.0
5 mdb005 F CIRC B 500 168 26.0


Code

from sklearn import preprocessing label_encoder = preprocessing.LabelEncoder() onehot_encoder = preprocessing.OneHotEncoder() train_y = label_encoder.fit_transform(info['class2']) train_y = train_y.reshape(len(train_y), 1) train_y = onehot_encoder.fit_transform(train_y)

LabelEncoder 결과


[0 0 2 2 0 0 2 2 2 2 0 2 0 0 2 0 2 0 2 0 2 0 2 1 2 0 2 2 1 2 0 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 0 2 2 2 0 2 2 2 2 2 0 2 2 1 2 2 1 2 2 2 2 0 0 2 0 2 2 2 2 2


OnehotEncoder 결과 - 최종 train_y

(0, 0) 1.0 (1, 0) 1.0 (2, 2) 1.0 (3, 2) 1.0 (4, 0) 1.0 (5, 0) 1.0 (6, 2) 1.0 (7, 2) 1.0 (8, 2) 1.0 (9, 2) 1.0 (10, 0) 1.0 (11, 2) 1.0 (12, 0) 1.0 (13, 0) 1.0

몇 줄 되지 않는 코드로 이처럼 one hot encoding을 구현할 수 있습니다. one hot encoding을 하기 전에 label encoding을 하는 이유는 one hot encoder의 인풋으로 숫자형만 올 수 있기 때문입니다. label encoder의 결과로 문자형 변수가 숫자형 변수 범주형으로 변경되게 되고 이를 one hot encoder에 fit_transform 해주면, 이와 같이 one hot encoding된 결과를 얻을 수 있습니다. 이 때, train_y 변수는 sparse matrix가되어 프린트하면 위와같이 나타납니다. 이는 matrix의 인덱스와 그에 해당하는 value를 나타낸건데 매트릭스에 값을 대입해보면 labelencoder 결과에 onehot encoding이 적용된 것을 확인할 수 있습니다.





Python - 폴더 파일 리스트 가져오기


python에서 운영체제 폴더 아래의 파일들을 불러올 때는 os 패키지를 이용한다. 아래 코드에서 filepath 부분에 운영체제 상에 폴더 path를 적으면 폴더 내부의 파일 이름을 가져올 수 있다.

from os import listdir
from os.path import isfile, join
files = [f for f in listdir('filepath') if isfile(join('filepath', f))]
['Info.txt',
 'Licence.txt',
 'mdb001.pgm',
 'mdb002.pgm',
 'mdb003.pgm',
 'mdb004.pgm',
 'mdb005.pgm',
 'mdb006.pgm',
 'mdb007.pgm',
 'mdb008.pgm',


코드를 실행하면 폴더 내부의 파일들의 이름이 files 리스트에 저장된다. 만약 여기서 특정 문자열을 포함한 파일들만 남기고 싶으면 아래와 같이 문자열의 기본함수인 find함수를 이용한다. -1을 리턴하면 해당 문자열이 없는 것이기 때문에 아래의 if문은 해당 문자열을 포함한 원소만 가져오게 한다.

files = [x for x in files if x.find("mdb") != -1] files

['mdb001.pgm',
 'mdb002.pgm',
 'mdb003.pgm',
 'mdb004.pgm',
 'mdb005.pgm',
 'mdb006.pgm',
 'mdb007.pgm',
 'mdb008.pgm'


  • yym 2018.08.07 02:56

    감사합니다. 도움이 되었습니다 ^^

  • nndk0043 2019.05.22 12:56

    댓글 달진 않지만 많은 분들이 참고하고 있어요. 감사합니다

    • Deepplay 2019.05.24 04:36 신고

      참고가 되었다니 좋네요 감사합니다 :)