데이터 분석 초보자를 위한 CS 기초 1: 클래스

Gayeon Kim·2020년 11월 13일
0

데이터 분석

목록 보기
7/9

1. df = pd.DataFrame()을 기억하시나요?


누군가 나에게 수학의 집합, 프로그래밍의 'Hello, world!' 같은 존재가 데이터 분석에서는 무엇이냐고 묻는다면 df = pd.DataFrame()을 꼽을 것이다. python으로 데이터 분석을 하는 사람이라면 지금까지 수도 없이 입력했을 명령어이다. df = pd.DataFrame()으로 데이터 프레임을 생성한 후에 df.head(), df.shape, df.isnull().sum() 등 많은 작업을 수행한다. 그런데 대체 df가, 혹은 pd.DataFrame이 뭔데 저렇게 많은 기능을 가지고 있는건지 궁금하지 않았는가?

DataFrame이 저렇게 많은 기능을 가지고 있는 이유는 DataFrame이 클래스(class)이기 때문이다. 클래스는 변수와 함수를 하나로 묶어놓은 것이다. 쉽게 말하면, 변수와 그와 관련한 함수를 사용하기 쉽게 하나의 상자에 담아두었고, 그 상자를 클래스라고 한다. 그리고 각 상자는 고유한 이름을 가지고 있다. 이젠 우리에게 친숙해진 데이터 프레임으로 생각해보자. 당신의 데이터 프레임에는 column의 이름 목록이나 index 번호 같은 정보가 있다. 그리고 당신은 데이터 프레임의 결측치도 한 번에 채우고 싶어서 fillna라는 함수를 만들었고, 데이터 프레임의 모양을 알 수 있는 shape라는 함수를 만들었다. 그런데 변수와 함수가 여기저기 흩어져 있으니 사용하기가 너무 불편했던 당신은 상자 하나를 구해와서 변수와 생성한 함수를 담고 DataFrame이라는 이름을 붙였다. 이때 변수와 함수를 담은 상자 DataFrame이 바로 클래스이다. 그리고 상자 안에 들어있는 함수를 메소드(method)라고 하며, 변수를 속성(attribute)이라고 한다.



2. 클래스(Class) 들여다보기


그럼 이제부터 클래스와 객체는 어떻게 만드는지, 그리고 어떻게 사용하는지 알아보자.

1) 클래스 생성

클래스를 생성하는 기본 코드는 아래와 같다.

# 클래스 생성
class Car:

  # 생성자 함수
  def __init__(self, color, door_num, wheel_shape, distance = 0):
    self.color = color
    self.door_num = door_num
    self.wheel_shape = wheel_shape
    self.distance = distance

  # 주행 메소드: 이동 거리와 총 주행 거리를 프린트함
  def move(self, dist):
    print('{}km를 이동하였다.'.format(dist))
    self.distance += dist
    print('총 주행거리(km):', self.distance)

먼저 __init__()은 생성자 함수이다. 클래스에 어떤 속성이 있는지 선언하는 부분이다. 그냥 함수를 만들 때 def func_name(param):과 같이 썼던 것을 기억하는가? 클래스도 마찬가지이다. 클래스를 만들 때 사용자가 입력해줘야 할 값들은 __init__()의 괄호 안에 적어준다. 그리고 move(self, dist)Car 클래스의 메소드이다. Car 클래스에는 color(색깔), door_num(문의 개수), wheel_shape(바퀴의 모양), distance(주행 거리)라는 속성이 있으며, move라는 메소드가 있다. 그런데 위의 코드를 보다보니 처음 보는 값이 하나 있다. 바로 self이다. self는 클래스를 사용해서 만든 객체가 들어가는 부분이다. 우선 아래 코드를 한 번 보자.


2) 객체 생성 및 사용

# 객체 생성
old_car = Car('red', 4, 'circle', 10000)
new_car = Car('silver', 2, 'circle')

# 객체 속성 값 출력
print(old_car.color)
print(old_car.door_num)
print()

print(new_car.color)
print(new_car.door_num)

[Out]

red
4

silver
2

완성된 클래스를 사용해서 객체를 만들 수 있다. 자동차 틀로 실제 자동차를 하나 찍어내는 것이다. Car이라는 틀(클래스)에 색깔, 문의 개수, 바퀴 모양, 주행 거리를 넣어 old_carnew_car라는 객체를 만들었다. 그런데 객체를 만드는 코드인 old_car = Car('red', 4, 'circle', 10)를 유심히 한 번 살펴보자. 어디서 많이 본 모양이지 않은가? 맞다. df = pd.DataFrame(raw_data)와 같은 모양이다. 우리가 여태까지 우리도 모르는 사이에 객체를 만들어서 사용하고 있었던 것이다.

그럼 이번엔 객체의 속성을 확인해보자. 객체의 속성은 old_car.color와 같이 객체명에 .<attribute>를 붙여서 접근할 수 있다. old_carnew_carCar라는 동일한 클래스를 사용해서 만든 객체이지만 속성의 값은 다르다. 차마다 색깔과 문의 개수가 다 다른 것처럼 말이다. 따라서 각 객체의 속성 값을 확인하기 위해서는 Car.attribute가 아니라 instance.attribute 를 해야 한다.

여기서 self의 비밀이 밝혀진다. self는 객체 자기 자신을 말한다. 아래 코드를 다시 한 번 살펴보자.

# 클래스 생성
class Car:

  # 생성자 함수
  def __init__(self, color, door_num, wheel_shape, distance = 0):
    self.color = color
    self.door_num = door_num
    self.wheel_shape = wheel_shape
    self.distance = distance

각 객체들은 클래스의 모든 내용을 복사해서 가지고 있지 않다. 그렇게 되면 메모리를 매우 많이 차지할 것이다. 따라서 객체들은 원래 클래스를 참조하며 명령을 실행한다. 따라서 각 객체마다 속성 값을 설정할 수 있도록 self를 사용해서 각 객체에 접근할 수 있도록 하는 것이다.

# 객체 메소드 사용
old_car.move(10)

[Out]

10km를 이동하였다.
총 주행거리(km): 10010

이번에는 메소드를 사용해보았다. 메소드도 속성처럼 instance.func()로 실행할 수 있다. 데이터 프레임의 결측치를 채우기 위해서 df.fillna('N/A')를 실행했던 것을 기억하는가? 우리는 이제 이 명령어가 어떤 뜻인지 더 정확하게 말할 수 있다. 'DataFrame이라는 클래스의 객체인 dffillna()라는 메소드를 실행해라'라는 뜻이다. 그리고 DataFrame 클래스를 만드는 코드 안에는 def fillna(self, param):과 같은 코드가 있을 것이다.



3. 상속


이미 만들어져 있는 클래스에 속성이나 기능을 추가하고 싶다면 어떻게 해야할까. 복사, 붙여넣기를 해서 필요한 내용을 기재해야 할까? 하지만 복사, 붙여넣기를 해서 다른 클래스를 만든다면 원래 클래스의 내용이 바뀔 때마다 이를 이용한 클래스를 모두 찾아서 하나하나 고쳐줘야 한다. 이처럼 같은 내용의 코드가 반복되는 건 관리를 하기에도 좋지 않으며, 작성에 있어서도 비효율적이다. 따라서 기존 클래스에 속성 및 기능을 추가하거나 재정의하고 싶을 때는 '상속'을 활용해서 새로운 클래스를 만든다.

# 부모 클래스 생성
class Car:

  # 생성자 함수
  def __init__(self, color, door_num, wheel_shape, distance = 0):
    self.color = color
    self.door_num = door_num
    self.wheel_shape = wheel_shape
    self.distance = distance

  # 주행 메소드: 이동 거리와 총 주행 거리를 프린트함
  def move(self, dist):
    print('{}km를 이동하였다.'.format(dist))
    self.distance += dist
    print('총 주행거리(km):', self.distance)
    
    
# 자식 클래스
class SuperCar(Car):

  # 속성 추가
  def __init__(self, color, door_num, wheel_shape, year, distance = 0):
    super().__init__(color, door_num, wheel_shape, distance)
    self.year = year

  # 함수 변경
  def move(self, dist):
    print('총 주행거리는', self.distance, 'km이다.')

  # 함수 추가
  def car_info(self, color, door_num, wheel_shape, year):
    print('color:', self.color)
    print('door_num:', self.door_num)
    print('wheel_shape:', self.wheel_shape)
    print('year:', self.year)
    
# 객체 생성    
super_car = SuperCar('blue', 5, 'circle', 2020, 100)
super_car.move(100)

[Out]

총 주행거리는 200 km이다.

원래 클래스를 부모 클래스, 상속 받아서 새롭게 만든 클래스를 자식 클래스라고 한다. 위 코드에서 Car가 부모 클래스, SuperCar가 자식 클래스이다. 부모 클래스에 있는 함수에 내용을 추가할 때는 super()를 사용한다. 원래 내용을 그대로 가져오겠다는 뜻이다. 반대로 부모 클래스에 있는 함수지만 내용을 완전히 바꾸고 싶을 때는 super() 없이 내용을 새로 작성하면 된다. 예를 들어 super_car.move(100)의 출력 결과를 보면 Carmove()와 전혀 다른 내용이 출력되었다.

상속은 우리가 만든 클래스를 상대로만 할 수 있는 게 아니다. 라이브러리, 패키지 등의 클래스 또한 상속받을 수 있다. 즉, Pandas의 DataFrame을 상속해서 우리가 필요한 메소드를 추가할 수 있다.



4. 마치며


지금까지 클래스에 대해서 알아봤다. 만약 당신이 오늘 클래스를 처음 봤다면 어려울 수 있다. Pandas, Scikit-Learn 등 훌륭한 패키지와 라이브러리가 많은데 왜 클래스를 알아야 하나 싶을 수도 있다. 하지만 우리는 지금까지 우리도 모르는 사이에 클래스를 사용하고 있었다. 지금은 'Hello, world!' 정도로 밖에 여겨지지 않는 df = pd.DataFrame()처럼 말이다. 클래스가 어렵게 느껴지는 건 내용이 어려워서가 아니라 익숙하지 않아서 그런 것이다. 클래스를 잘 이해한다면 기존의 패키지에 우리가 필요한 내용을 추가할 수 있으며, 또 기존 패키지를 그대로 사용한다 하더라도 내가 입력하는 명령어가 어떻게 작동하는 것인지 알고 입력한다면 더 효율적으로 코드를 작성할 수 있다. 그러니 차근차근 내용을 살펴보며 클래스에 익숙해져 보자.



0개의 댓글