Numpy | 선형대수학 기초, 행렬 곱 연산(dot(), *, @)

선형대수학 기초와 numpy 함수, 행렬의 곱 연산 차이

import numpy as np

행렬

n차원 행렬 만들기

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

A는 (2, 3)의 matrix, 행렬

행렬 연산

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

B = np.array([[2, 2, 2],
            [3, 3, 3]])

행렬끼리 더하기

print(A+A)
'''
[[ 2  4  6]
 [ 8 10 12]]
'''
print(A+B)
'''
[[3 4 5]
 [7 8 9]]
 '''

행렬끼리 빼기

print(A-A)
'''
[[0 0 0]
 [0 0 0]]
'''
print(A-B)
'''
[[-1  0  1]
 [ 1  2  3]]
 '''

중요한 행렬의 곱셈 (* vs. @ vs. dot())

행렬의 곱셈은 어렵기 때문에, 자세히 살펴봐야 한다.

아래 코드 실행 결과를 예상해보자.

X = np.array([[1, 2],
            [3, 4]])
Y = np.array([[1, 1],
            [2, 2]])

print(X*Y) # 1
print(X.dot(Y)) # 2
print(X@Y) # 3

1. * 연산

numpy 연산자 *는 matrix의 같은 위치에 있는 원소끼리 곱한다.

print(X*Y) # 1
'''
[[1 2]
 [6 8]]
'''
  • numpy * operator: X*Y
  • element-wise
  • 연산하려는 matrix shape이 동일해야 한다. (n, m) * (n, m) = (n, m)
  • np.multiply(X, Y)를 연산자 *로 축약해서 사용함

2. np.dot() 연산

numpy.dot: 두 vector의 내적을 계산하는 함수

피연산자의 array 차원에 따라 계산 방법이 조금씩 다르니 아래의 설명 혹은 공식 문서를 참고

print(X.dot(Y)) # 2
'''
[[ 5  5]
 [11 11]]
'''
  • numpy.dot: X.dot(Y)
  • X, Y가 모두 1차원 array(vector) -> 벡터의 내적 연산
  • X, Y가 모두 2차원 array(행렬) -> 행렬 곱 연산 (그러나 matmul이나 X@Y 사용을 권장)
  • X, Y 둘 중에 하나가 scalar 값이라면 -> 곱셈연산 (그러나 np.multiply(X, Y) 혹은 X*Y 사용을 권장)
  • X가 N차원 array, Y가 1차원 array(vector) -> X의 마지막 axis 기준으로 sum 연산
  • X, Y가 모두 2차원 이상의 array(텐서)이라면 -> X의 마지막 축, Y의 마지막에서 두번째 축의 기준으로 sum 연산

    dot(a, b)[i,j,k,m] = sum(a[i,j,:] * b[k,:,m])

# N-D array, 1-D array dot 연산 예시
a = np.arange(8).reshape(2, 4); 
'''
array([[0, 1, 2, 3],
       [4, 5, 6, 7]])
'''
b = [10, 10, 10, 10]
a.dot(b)
### >>> array([ 60, 220])
# N-D array, 1-D array dot 연산 예시
c = np.arange(16).reshape(2, 2, 4)
'''
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])
'''
d = [1, 1, 1, 1]
c.dot(d)
### >>> array([[ 6, 22],
### >>>    [38, 54]])

3. matmul() 과 @

numpy.matmul: 다차원 array의 곱셈을 수행한다.

X = np.arange(2*3*4).reshape((2, 3, 4))
'''
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]
'''
Y = np.arange(2*3*4).reshape((2, 4, 3))
'''
[[[ 0  1  2]
  [ 3  4  5]
  [ 6  7  8]
  [ 9 10 11]]

 [[12 13 14]
  [15 16 17]
  [18 19 20]
  [21 22 23]]]
'''

print(np.matmul(X, y).shape)
### >>> (2, 3, 3)

# 아래 두 연산 동일
print(X@Y) # 3
print(np.matmul(X, Y)) 

'''
[[[  42   48   54]
  [ 114  136  158]
  [ 186  224  262]]

 [[ 906  960 1014]
  [1170 1240 1310]
  [1434 1520 1606]]]
'''

The @ operator can be used as a shorthand for np.matmul on ndarrays.

  • np.matmul() 연산을 @ 연산자로 줄여서 나타낼 수 있다.
  • X의 마지막 차원 = Y의 마지막에서 두번째 차원(second-to-last)이 같아야 함

    (n, k) (k, m) = (n, m)과 똑같다. k로 같아야 함

  • 결국 matmul은 마지막 (n, m) 2차원을 박스로 보고, 그 박스가 몇 개 (몇 차원)인지

그래서 dot과 matmul

  • dot()은 N-D array와 scalar끼리 연산 가능 (matmul은 피연산자가 모두 N-D array)
  • 2차원 행렬: dot()matmul()의 연산 결과 동일
  • 3차원 이상 array: dot()matmul() 연산 결과가 다름
a = np.ones([9, 5, 7, 4])
c = np.ones([9, 5, 4, 3])

np.dot(a, c).shape
### >>> (9, 5, 7, 9, 5, 3)
np.matmul(a, c).shape
### >>> (9, 5, 7, 3)

# 앞에 차원 제외하고 뒤에 2차원 행렬 곱처럼 (7, 4) x (4, 3) = (7, 3)으로 나왔다고 생각

정리

  • 행렬(2차원) 곱에는 np.dot()과 matmul()의 연산 결과가 같다.
  • 그러나 고차원 배열 혹은 텐서의 곱셈에서는 계산 결과가 전혀 다르다.
  • 텐서에 대해서는 나중에 정리해야지



Reference

Basic | Simple Linear Regression

선형 회귀에 대하여, 그리고 python과 사이킷런으로 단순선형회귀모델 구현하기

Regression 이란

regression = 추세선

ML 분류

  • 지도학습
    1. regression
      1. linear regression
        • 단순 선형회귀
        • 다중 회귀분석
        • 다항 회귀분석
      2. logistic regression
      3. K-Nearest Neightbors regression
      4. Decision Tree
    2. classification
  • 비지도학습
  • 강화학습

지도 학습 중 하나로, 입력값에 따른 미래 결과값을 예측하는 알고리즘

ex. 몸무게-키 데이터에서 몸무게에 따른 키 예측하기

Simple Linear Regression

단순 선형 회귀

  • 하나의 X (input) -> Y (output) 예측
  • 직선 하나
  • 기울기(\(\beta_1\))와 절편(\(\beta_0\)) 찾기

선형회귀의 목적: \(Y = \beta_0 + \beta_1X\) 의 직선 찾기

좋은 직선 긋기, Loss Function

좋은 직선이란? 실제값(y)과 예측값(\(\beta_0X + \beta_1\) )의 차이가 적을수록 좋다.

실제값: input에 대한 실제 output
예측값: input에 대한 예측값, 직선에 x를 넣었을 때 나오는 값

Loss Function (손실함수) \[\sum_{i}^n (y_i - (\beta_0 + \beta_1x_i))^2\]

  • = Cost Funtion
  • loss = 예측값과 실제값의 차이
  • 기계학습을 통해 결국 최소로 만들고자 하는 것,학습은 loss를 줄이는 방향으로 진행된다.

차이를 제곱해서 더하는 이유: 차이가 음수, 양수 모두 존재한다면 전체 차이를 구하기 위해 모두 더할 때 음수, 양수 차이가 상쇄 되어 버린다. -> 실제로는 차이가 많이 나도 전체 차이는 0이 나올 수도 있다

좋은 직선, beta 값을 찾는 법

Gradient Descent (경사하강법)

  • loss function을 최소로 만드는 \(\beta_0 \beta_1\) 찾기
  • 현재 위치에서 기울기가 가장 낮은 쪽으로 이동
  • 현재 위치에서 기울기(미분값, 경사)를 구하고 경사의 반대 방향으로 이동시키면 극값이 나온다.
  • 경사의 반대 방향 의미: 어떤 점에서 기울기가 양수라면 우상향 그래프, 최솟값으로 가기 위해서는 왼쪽(음수 방향)으로 이동해야 하고, 기울기가 음수라면 우하향 그래프이므로 최솟값으로 오른쪽(양수 방향)으로 이동해야 한다.
  • 다음 점 위치:
  • 지역 최적해로 수렴, 전역 최적해 보장 X

    출처: 리브레 위키

Scikit-learn을 이용한 회귀분석

import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import LinearRegression

# loss function 구현
def loss(x, y, beta_0, beta_1):
    N = len(x)

    l = 0 # loss 값
    for i in range(N):
        l += (y[i] - (beta_0 + beta_1*x[i]))**2
    
    return l

# sample data
X = [8.70153760, 3.90825773, 1.89362433, 3.28730045, 7.39333004, 2.98984649, 2.25757240, 9.84450732, 9.94589513, 5.48321616]
Y = [5.64413093, 3.75876583, 3.87233310, 4.40990425, 6.43845020, 4.02827829, 2.26105955, 7.15768995, 6.29097441, 5.19692852]

train_X = np.array(X).reshape(-1, 1) # 10x1로 reshape
train_Y = np.array(Y).reshape(-1, 1)

###########
### 모델 객체 생성 및 train
lrmodel = LinearRegression()
lrmodel.fit(train_X, train_Y)

beta_0 = lrmodel.intercept_ # lrmodel로 구한 직선의 y절편
beta_1 = lrmodel.coef_[0]   # lrmodel로 구한 직선의 기울기

###########
### 학습 결과 출력
print("beta_0: %f" % beta_0)
print("beta_1: %f" % beta_1)
print("Loss: %f" % loss(X, Y, beta_0, beta_1))

plt.scatter(X, Y)
plt.plot([0, 10], [beta_0, 10 * beta_1 + beta_0], c='r')

plt.xlim(0, 10) # 그래프의 X축을 설정합니다.
plt.ylim(0, 10) # 그래프의 Y축을 설정합니다.

Reference

https://karupro.tistory.com/99 https://brunch.co.kr/@mnc/9 https://librewiki.net/wiki/%EA%B2%BD%EC%82%AC%ED%95%98%EA%B0%95%EB%B2%95

Basic | 정규표현식, python re 모듈 사용하기

정규표현식이란 무엇인가, 파이썬에서 정규표현식 사용하기 (메타문자, 수량자, 파이썬 모듈 re 사용하기)

모르면 완전 암호 같지만 규칙만 알면 쉬운 정규표현식!

정규표현식(regex)이란

= 정규식

  • 문자열에서 특정 문자 조합을 찾기 위한 패턴
  • ex. 전화번호, 이메일, 웹사이트 주소를 검색하거나, 형식을 지정할 때 사용

메타 문자

수량자

그룹

  • 정규식에서 괄호 ()로 묶음 (abc)*de(fg)+
  • 그룹 참조하기: \1, \2 등 순서대로
  • 그룹 참조하여 정규식에 나타낼 때, 이스케이프 문자 \를 한 번 더 사용해야 함 ` “abc\1”`

파이썬에서 정규식 사용하기

import re

1. findall 함수

re.findall(패턴문자열, 타겟문자열)

  • 반환형: list
  • 타겟문자열에서 pattern과 일치하는 것을 모두 찾기

ex. 전화번호 형식 010-xxxx-xxxx 모두 찾기

s = "lee 010-2222-3333 kim 010-5959-5958 park 01011112222"
p = "010-\d{4}-\d{4}"

print(re.findall(p, s))
### >>> ['010-2222-3333', '010-5959-5958']

2. search 함수

re.search (패턴문자열, 타겟문자열)

  • 반환형: Match object
  • 전체 문자열 중에 매치되는 오브젝트 찾기
  • re.match()와 다른점: match는 선두에 매치되는 문자열이 없으면 None 반환하지만 search는 전체 문자열 탐색
  • match object에 group() 함수 사용하여 매치된 결과 확인
s = "tomato"
p = "(to)ma\\1"
m = re.search(p, s)

print(m1.group())
### >>> tomato

3. sub 함수

re.sub(패턴문자열, 대체할 문자열, 타겟문자열)

  • 반환형: string
  • 타겟문자열에서 패턴문자열을 찾고 특정 형식으로 대체하기
  • 대체할 문자열 파라미터에 정규식 사용 가능

ex. 유효한 전화번호는 vaild! 문구로 대체

s = "lee 010-2222-3333 kim 010-5959-5958 park 01011112222"
p = "(010)\-\d{4}\-(\d{4})"
print(re.sub(p, "\g<1>-****-\g<2>", s))

'''
lee vaild! 
kim vaild! 
park 01011112222
'''

ex. 전화번호 가운데 네 자리 블라인드(**) 하기

s = "lee 010-2222-3333 kim 010-5959-5958 park 01011112222"
p = "(010)\-\d{4}\-(\d{4})"
print(re.sub(p, "\g<1>-****-\g<2>", s))

'''
lee 010-****-3333 
kim 010-****-5958 
park 01011112222
'''

g<1> : 그룹 설정, 앞에서부터 1, 2, 3으로 이름 붙음



Reference

  • https://docs.python.org/3/library/re.html

틱택토 게임 구현

python으로 틱택토 게임 구현하기, class 객체 사용하기

틱택토 게임을 구현해봤다. 파이썬은 C/C++에 비해서 문자열 비교가 너무 쉬워서 너무 행복하다.

### 22.10.06
### 틱택토 게임 구현
### class 변환해서 구현

import time


class TicTacToe:
    def __init__(self):
        self.board = {(i + 1): " " for i in range(9)}
        self.turn = 0
        self.player = "B"
        self.markers = {"A": "X", "B": "O"}

    def print_board(self):
        print("")
        for i in range(1, 10):
            print(f"{i}: {self.board[i]} ", end=" ")
            if i % 3 == 0:
                print("")

    #####################
    ### player 전환
    def next_player(self):
        if self.turn % 2 == 0:  # A 차례
            return "A"
        else:
            return "B"

    ######################
    ### input 관리
    def insert_input(self, player, marker):
        input_slot = int(input(f"{player} >> marker({marker}) 표시 위치를 선택하세요(1~9): "))

        while input_slot < 1 or input_slot > 9 or self.board[input_slot] != " ":
            input_slot = int(input(f"{player} >> 적절한 범위와 빈 슬롯의 번호를 다시 입력하세요(1~9): "))

        self.board[input_slot] = marker
        self.turn += 1

    ################
    ### 승패 결정: 승부가 끝났다면 True 반환
    def win_game(self):
        # 세로
        for start in range(1, 4):
            if self.board[start] != " " and self.board[start] == self.board[start + 3] == self.board[start + 6]:
                return True
        # 가로
        for start in range(1, 9, 3):
            if self.board[start] != " " and self.board[start] == self.board[start + 1] == self.board[start + 2]:
                return True

        # 대각선
        if self.board[5] != " " and \
                (self.board[1] == self.board[5] == self.board[9] or self.board[3] == self.board[5] == self.board[7]):
            return True

    ################
    ### 게임을 더 할 건지
    def play_again(self):
        c = input(f"New game? (y/n): ")
        if c == "y":
            self.__init__()
            return True
        else:
            return False

    ################
    ### 메인 게임 실행 함수
    def game(self):
        cont = True # continue game?

        while cont:
            self.print_board()
            self.player = self.next_player()  # self.count 기준으로 다음 순서 정하기

            ### marker 위치 input 받기
            self.insert_input(self.player, self.markers[self.player])

            ### 승패 판정
            res = self.win_game()

            ### 게임 끝, 다음 게임 진행 여부
            if res:
                self.print_board()
                time.sleep(0.5)
                print(f"{self.player} WIN -------------------\n")
                cont = self.play_again()  # 새로운 게임

            if not res and self.turn == 9:  # 무승부 판정
                time.sleep(0.5)
                print(f"TIE GAME -----------\n")
                cont = self.play_again()  # 새로운 게임


ttt = TicTacToe()
ttt.game()

Basic | Set 자료형

파이썬 자료구조 set에 대해 알아보자

집합 (set) 자료구조가 가지는 특징, 함수들은 뭐가 있을까

집합의 특징

  • 중복이 없다
  • 순서가 없다

집합 만들기

  • 딕셔너리와 똑같이 중괄호 {}로 표현
  • 딕셔너리와 다르게 key:value 형식이 없음, 원소만 나열
# 넷 다 같은 집합
set1 = {1, 3, 5}
set2 = set([1, 3, 5]) # set() 함수를 사용해 list to set 변환
set3 = set([5, 3, 1])
set3 = {3, 1, 3, 5} # 집합은 중복 원소 표시하지 않음 

집합 원소 추가/삭제


myset = {1, 2, 3, 4}

# 원소 추가
myset.add(5)

# 여러 개의 원소 한 번에 추가
myset.update([6, 7, 8])

# 원소 제거
myset.remove(5) # 존재하지 않는 원소를 삭제하려고 한다면 에러

# 원소 제거
myset.discard(10) # 존재하지 않는 원소라면 아무것도 하지 않음

집합 연산

  1. 교집합
  2. 합집합
  3. 차집합
  4. XOR (Exclusive or)
# 합집합
union = set1 | set2

# 교집합
intersection = set1 & set2

# 차집합
diff = set1 - set2

# xor
xor = set1^set2

Basic | 특정 데이터 가져오기 (operator.itemgetter)

itemgetter()는 특정 인덱스, key 값을 가진 데이터를 반환

from operator import itemgetter

python의 operator 모듈의 함수 중 하나, operator.itemgetter()

Return a callable object that fetches item from its operand using the operand’s getitem() method.

말이 어렵게 써있지만,

  • list, tuple, dictionary 등의 데이터에서 특정 항목을 가져온다.
  • sorted(), map()에서 활용 가능하도록 호출 가능한 형태를 반환

예시: sorted

학생들의 시험점수를 나타낸 리스트가 있고,

exam = [
    ("Alice", 80),
    ("Jane", 95),
    ("Peter", 72)
]

시험 점수가 높은 학생 순으로 정렬하고 싶다면, sorted()에 들어갈 key 함수에 점수 기준으로 정렬하도록 설정해야 한다.

# 1. 리스트의 각 원소(학생)의 시험점수를 반환하는 함수를 만들고
def get_score(student):
    # (이름, 점수) 형식의 튜플
    return student[1]

# 2. def_score 함수를 key로 넘겨주어 exam 데이터 속의 점수를 기준으로 정렬할 수 있도록 한다.
sorted_students = sorted(exam, key=get_score, reverse=True)

이때 itemgetter()를 사용한다면 get_socre 함수를 만들지 않고, 특정 원소를 바로 반환받을 수 있다.

# itemgetter(1): 1번째 원소를 반환
sorted_students = sorted(exam, key=itemgetter(1), reverse=True)

작동 원리

itemgetter(1)("ABCDEFG")
### >>> 'B'

itemgetter(0, 2)("ABCDEFG") # 여러 개의 값이 선택되면 tuple 형태로 반환
### >>> ('A', 'C')

f = itemgetter(2) 라면 f(r) 호출 후 r[2] 반환하는 형태와 같다.

itemgetter()의 구현은 다음과 같다. 근데 알 필요가 없죠

def itemgetter(*items):
    if len(items) == 1:
        item = items[0]
        def g(obj):
            return obj[item]
    else:
        def g(obj):
            return tuple(obj[item] for item in items)
    return g

활용: map

map 함수는 iterable한 모든 항목에 지정한 funciton을 적용한 후 그 결과를 반환 (정확히는 결과를 내는 iterator를 반환한다, 반환형도 list가 아니라 map object)

inventory = [('apple', 5), ('banana', 10), ('orange', 2), ('kiwi', 1)]

# map: inventory라는 리스트에 itemgetter(0) 함수를 동일하게 적용
# itemgetter: inventory라는 리스트에서 0번째 item을 가져옴
list(map(itemgetter(0), inventory))

### >>> ['apple', 'banana', 'orange', 'kiwi']

활용: dictionary key

itemgetter()의 매개변수로 dictionary 자료형의 key 값을 넣어줄 수 있다.

students = [
    {'name': 'Alice', 'age': 19, 'score': 80},
    {'name': 'Jane', 'age': 17, 'score': 95},
    {'name': 'Peter', 'age': 16, 'score': 72},
]

sorted(students, key=itemgetter('age'))
### >>> [{'name': 'Peter', 'age': 16, 'score': 72},
###     {'name': 'Jane', 'age': 17, 'score': 95},
###     {'name': 'Alice', 'age': 19, 'score': 80}]


Reference

  • https://docs.python.org/3/library/operator.html#operator.lt
  • https://wikidocs.net/109327

Basic | Python 문자열 함수

파이썬 문자열에 자주 사용하는 함수

파이선 문자열을 다룰 때 쓰는 대표 함수들을 알아보자. (리스트 함수 포함 ㅎ)

목차

  1. startswith()
  2. split()
  3. lower()
  4. replace()

startswith()

string.startswith("<특정문자열>")

문자열이 특정 문자열로 시작하는지 검사, True/False 반환

s = "I'm banana"

print(s.startswith("I")) # True

if s.startswith("I"):
  print("문자열 s는 I로 시작함")

if s.startswith("I'm"):
  print("문자열 s는 I'm로 시작함")

split()

string.split("<기준 문자>")

문자열을 특정 기준 문자로 쪼개서 리스트로 반환

s = "I'm banana"

# split() 함수 안에 기준 문자가 없을 때: 공백 기준으로 문자열을 split
print(s.split()) 
# >>> ["I'm", "banana"]

print(s.split("'")) 
# >>> ["I", "'m banana"]

split() vs. split(‘ ‘)

split(): 공백 전부 제거
split(' '): 공백 기준으로 모두 split, 빈 문자열이 반환됨

numbers = "  1 2 3  "
print(numbers.split())
# >>> ['1', '2', '3']

print(numbers.split(' '))
# >>> ['', '', '1', '', '2', '', '3', '', '' ]

lower()

문자열의 대소문자 변환

s = "I'm banana"

print(s.lower())
# >>> i'm banana

print(s.upper())
# >>> I'M BANANA

주의!
s.lower()는 문자열을 직접 수정하지 않고 새로운 값 생성.

s = "I'm banana"
s.upper()
print(s)
# >>> I'm banana

s = s.upper() # 꼭 반환값을 새로운 문자열 변수나 기존 변수에 넣어주도록 하자
print(s)
# >>> I'M BANANA

replace()

문자열.replace("기존 문자열", "대체할 문자열")

특정 문자열로 대체

s = "I'm banana"

print(s.replace("I'm", "I am"))
# >>> I am banana

# 대체할 문자열에 아무것도 입력하지 않으면 문자 제거로 활용할 수 있음
print(s.replace("'", ""))
# >>> Im banana

주의
마찬가지로 replace()는 문자열을 직접 수정하지 않고 새로운 값을 생성한다.

Baisc | List Comprehension 문법

리스트로 리스트 만들기

리스트 생성을 한 줄로 하기


List Comprehension 이란

리스트를 생성할 때 짧게 한 줄로 만드는 문법

[<원소의 최종 형태> for <변수> in <순회 가능한 , 리스트 >]

예시: list로 list 만들기

이미 존재하는 list의 값을 변형하여 새로운 list를 만드려고 할 때의 예시

# 요일의 맨 첫 글자만 따서 새로운 리스트를 만드려고 할 때
days = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"]

new_days = []
for day in days:
  new_days.append(day[0])

print(new_days)
# >>> ['M', 'T', 'W', 'T', 'F', 'S', 'S']

위와 같이 for문으로 순회하면서 만들어내야 하는 리스트를 한 줄로 짧게 만들 수 있게 하는 문법이 리스트 컴프리헨션이다.

# list comprehension 
days = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"]

new_days = [day[0] for day in days]
# >>> ['M', 'T', 'W', 'T', 'F', 'S', 'S']

예시: range() 활용

range() 함수는 특정 범위 값의 정수 리스트를 반환하므로 아래와 같이 활용할 수 있다.

# 0부터 20미만 짝수 list 만들기
even_nums = [i*2 for i in range(10)]

# >>> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

예시: if문 활용

# 0부터 20미만 짝수 list 만들기
even_nums = [i for i in range(20) if i%2==0]

# >>> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]


Pandas | Pandas 심화 (데이터 검색, 조건)

pandas 자주 쓰는 함수 정복하기: query(), apply(), groupby()

목차

  1. 데이터 조건 검색하기(query, str.contains)
  2. 데이터에 함수 적용하기(apply, lambda)
  3. 데이터 조건으로 묶기(groupby, aggregate)
  4. 멀티인덱스
  5. Pivot Table


import pandas as pd

데이터: 조건 검색

1. masking 연산

numpy와 마찬가지로 마스킹 연산 (True, False 반환)으로 원하는 row만 뽑을 수 있다.

df = pd.DataFrame(np.random.rand(5, 2), columns=["A","B"])

print(df[df["A"] < 0.5])
print(df[(df["A"] < 0.5) & (df["B"] > 0.3)] # 조건 여러개

making 연산 실행 예시

2. query() 함수 사용

pandas.DataFrame.query 공식문서

df = pd.DataFrame(np.random.rand(5, 2), columns=["A","B"])

df.query("A<0.5")
df.query("A<0.5 and B>0.3") # 조건 여러 개: query 문자열 안에 and or 사용

3. 문자열 검색

# 1. # Cat을 포함한 문자열 찾기
df["Animal"].str.contains("Cat") 

# 2. # Cat과 완전히 매칭되는 문자열 찾기
df.Animal.str.match("Cat") # 특징: match() 안에 정규표현식 사용 가능

# 3. 2와 같음
df["Animal"] == "Cat"

데이터: 함수 처리

dataframe의 데이터에 함수 적용하기

df = pd.DataFrame(np.arange(5), columns="Num")

def square(x): # 제곱하는 함수
  return x**2

# Num 열의 데이터들을 제곱하여 새로운 열 Square을 추가해보자
# 1. 만들어둔 square 함수 사용
df["Square"] = df["Num"].apply(square)

# 2. 람다식 사용
df["Square"] = df["Num"].apply(lambda x:x**2)

데이터: 그룹으로 묶기 (조건부 집계)

1. groupby()

특정 열을 기준으로 데이터를 집계

df = pd.DataFrame({"key": list("ABCABC"), "col1": [1, 2,3,1,4,3,], "col2": np.random.randint(0, 6, 6)})

df 출력

위와 같은 데이터가 있을 때 key값을 기준으로 묶어 합계를 내고 싶다면.

# key 열을 기준으로 묶음 -> 합계
df.groupby("key").sum()

# 두 개의 열을 기준으로 묶음 -> 평균
df.groupby(["key", "col1"]).mean()

결과

2. aggregate()

groupby를 통해 집계를 한 번에 계산하는 함수

df.groupby(''' /기준열/ ''').aggregate(''' /list or dict/ ''')

# 1. 리스트 형태로 각 열에 공통적으로 적용
df.groupby("key").aggregate([min, np.median, max])

# 2. 딕셔너리 형태로 각 열마다 다른 집계 수행
df.groupby("key").aggregate({"data1":min, "data2":sum})

각 열의 min, median, max 값 한 번에 계산

3. filter()

groupby를 통해 그룹 속성을 기준으로 데이터 필터링

  • groupby와 함께 사용하여 특정 기준을 만족하는 데이터 row만 출력
  • column 별 계산
def filter_mean(x):
    return x["col2"].mean() > 3

df.groupby("key").filter(filter_mean)

결과

col2의 mean이 3초과인 데이터 ()= key가 C인) row(2, 5)만 필터링

4. apply()

groupby를 통해서 묶은 데이터에 함수 적용

  • apply를 groupby와 함께 사용할 수 있다.
  • column 별 계산

멀티인덱스

인덱스가 중복될 때, 계층적으로 만들어진다.

  • row, col 모두 계층적으로 생성
  • iloc, loc 사용 가능
df = pd.DataFrame(np.random.randn(10, 4), index=[list("AABBBAAABB"), [1, 2, 1, 1, 2, 1, 2, 2, 1, 1, ]], columns=[["col1", "col1", "col2", "col2"], [1, 2, 3, 4]])

df 출력 결과

# 멀티인덱스 접근 예시
df["col1"][1]

Pivot Table

데이터에서 필요한 자료만 뽑아서 새롭게 요약

  • index: 행 인덱스로 들어갈 key
  • column: 열 인덱스로 라벨링될 값
  • value: 분석할 데이터 (값)
# 일차별로, (row)
# 사람들의 평균 나이를 구함 (value)
# 성별로 라벨링 (col)
tb = df.pivot_table(
  index="일차", columns="성별", values="나이",
  aggfunc=np.mean # value 값을 어떻게 처리할건지
)

print(tb)

결과

Pagination