728x90
반응형

  예를 들어 당신이 어떠한 반복되는 문자열을 만드려고, 하고 그 중 일부분만 바꿔야 한다면 어떻게 하겠는가?

# 예를 들어 뽀로로, 크롱의 인사와 짱구, 짱아의 인사 문자열을 생성하려면 어떻게 해야할까?
>>> str0 = "뽀로로: \"크롱 반가워!\"\n크롱:\"오! 뽀로로 좋은 아침이야!\""
>>> str1 = "짱구: \"짱아 반가워!\"\n짱아:\"오! 짱구 좋은 아침이야!\""
>>> print(str0)
>>> print("\n")
>>> print(str1)
뽀로로: "크롱 반가워!"
크롱:"오! 뽀로로 좋은 아침이야!"

짱구: "짱아 반가워!"
짱아:"오! 짱구 좋은 아침이야!"

 위 예시처럼 일일이 그 문자열을 만들어 새로 지정해 줄 것인가? 이는 매우 비 경제적인 방법인데, 이번 포스트에선 문자열의 일부를 손쉽게 바꿀 수 있는 방법인 문자열 포맷팅에 대해 학습해보겠다.

 

 

문자열 포맷팅(String Formatting)

 지난 포스트인 이스케이프 문자 예제에서 뽀로로, 크롱과 같은 문자열 내 특정 문자가 반복되어 출력되는데, 이 부분만 자동으로 바꿀 수 있다면, 보다 적은 코드만 써도 되지 않을까? 라는 생각이 들지 않는가?

 문자열 포맷팅은 코드를 더 경제적으로 쓸 수 있는 방법 중 하나로, 출력되는 문자열에서 일부분만 쉽게 바꿀 수 있는 방법 중 하나다.

 문자열 포맷팅은 크게 3가지 방법이 있다.

  1. % formating
  2. {} formating
  3. f-string

 

 

1. % formating

# 1. % formating
>>> print("%s: %s, 반가워!" %("뽀로로", "크롱"))
>>> print("%s: %s, 좋은 아침이야!" %("크롱", "뽀로로"))
뽀로로: 크롱, 반가워!
크롱: 뽀로로, 좋은 아침이야!
  • 위 예시를 보면, %s라는 문자와 %()라는 처음 보는 문자가 들어간 것을 볼 수 있다.
  • %s %d % ("str", 2) 이 기본적인 방식이다. 여기서 %s, %d와 같은 문자를 문자열 포맷 코드라고 한다.
  • 문자열 포맷 코드는 다음과 같다.
코드 설명 코드 설명
%s 문자열(string) %d 정수(integer)
%c 문자 1개(character) %f 부동소수(floating-point)
  • 참고로 %s의 경우 문자형이나, 숫자, 소수 모두 문자형으로 변형이 가능하므로, %s를 넣는 경우, 모든 형태의 값을 넣을 수 있다.
  • 이 밖에도 8진수(%o), 16진수(%x)가 더 있긴 하지만, %s, %d, %f만 사용할 수 있어도 충분하다.
  • 참고로 %를 문자로 그냥 출력하고 싶다면 %%를 입력해야한다.
# 위 코드를 좀 더 쉽게 써보자
>>> name1 = "뽀로로"
>>> name2 = "크롱"
>>> print("%s: %s, 반가워! \n%s: %s, 좋은 아침이야!" %(name1, name2, name2, name1))
뽀로로: 크롱, 반가워! 
크롱: 뽀로로, 좋은 아침이야!

>>> name3 = "짱구"
>>> name4 = "짱아"
>>> print("%s: %s, 반가워! \n%s: %s, 좋은 아침이야!" %(name3, name4, name4, name3))
짱구: 짱아, 반가워! 
짱아: 짱구, 좋은 아침이야!
  • 위 코드 같이 반복 출력하고자 하는 Data("뽀로로", "크롱")를 변수(name1, name2)에 담아서 사용하면 보다 쉽게 쓸 수 있다.
  • 그러나 위 방법의 경우, 문자를 넣어주고 싶은 위치랑 %()안의 변수 위치가 동일해야하므로, Data가 문자열에서 들어갈 위치를 헷깔릴 수 있다는 단점이 있다.

 

 

 

2. {} formating

# {} formating을 써보자
print("{name1}: {name2}, 반가워!\n{name2}: 오! {name1} 좋은 아침이야!".format(name1="뽀로로", name2="크롱"))
뽀로로: 크롱, 반가워!
크롱: 오! 뽀로로 좋은 아침이야!
  • {} format은 문자열 내에 원하는 변수를 지정하여 사용할 수 있다.
  • % formating과 달리 변수의 위치를 구체적으로 알 수 있다.
  • 이를 def 코드를 사용해서 함수로 만든다면, 보다 효율적으로 사용할 수 있다.
# def 코드를 사용해서 만들어보자
>>> def hello(name1, name2):
>>>     print("{name1}: {name2}, 반가워!\n{name2}: 오! {name1} 좋은 아침이야!".format(name1=name1, name2=name2))
    
>>> hello("짱구", "짱아")

짱구: 짱아, 반가워!
짱아: 오! 짱구 좋은 아침이야!
  • 그러나, 이 방법은 .format(변수이름=변수)를 넣어줘야하기 때문에 코드가 길어진다는 단점이 있다.

 

 

 

3. f-string

% formating과 {} formating을 비교해보면, 각각 코드를 만드는 데에 있어 장점과 단점이 나뉘는 것을 알 수 있다.

  • % formating은 문자열 포맷팅 밖에서 지정한 변수를 가져갈 수 있다.
  • {}.formating은 문자열 내 포맷팅 된 변수들의 위치를 알 수 있다.
  • 위 장점을 적절히 섞는다면 보다 코드를 수월하게 짤 수 있지 않을까? > f-string을 통해 이를 해결할 수 있다.
# f-string 포맷팅을 사용해보자
>>> name1 = "뽀로로"
>>> name2 = "크롱"
>>> print(f"{name1}: {name2}, 반가워!\n{name2}: 오! {name1} 좋은 아침이야!")
뽀로로: 크롱, 반가워!
크롱: 오! 뽀로로 좋은 아침이야!
  • f-string은 위 % formating이나 {} formating보다 코드가 단순하다.
  • 문자열 밖에서 문자열에 들어갈 가변적인 변수를 지정하고, 문자열 앞에 f를 붙이면 된다.
  • 문자열 내에선 {} formating과 같은 방식으로 문자열을 적으면 된다.
  • 위 코드를 def 코드를 이용해서 함수로 만들어보자.
# 함수화 해보자
>>> def hello(name1, name2):
>>>     print(f"{name1}: {name2}, 반가워!\n{name2}: 오! {name1} 좋은 아침이야!")
    
>>> hello("짱구", "짱아")


짱구: 짱아, 반가워!
짱아: 오! 짱구 좋은 아침이야!
  • 위의 {} formating을 사용해서 만든 함수보다 코드가 단순하다는 것을 알 수 있다.

 

 

 

 지금까지 문자열의 일부만 바꾸는 방법인 문자열 포맷팅에 대해 알아보았다. 이 중 f-string이 가장 코드가 단순하고, 직관적이므로, 가능하면 f-string을 쓰길 바란다.

 다음 포스트에서는 문자열을 가지고 노는 방법인 문자열 전처리에 대해 학습해보겠다.

728x90
반응형
728x90
반응형

 지난 포스트에서는 Python의 기본적인 자료형에 대해 공부해보았다. "Python-기초: 1.0. 자료형(1) - scalar"에서 Data type으로 int, float, None, Boolean은 나왔으나, 정작 문자열은 나오지 않았다.

 이번 포스트에서는 data type 중 문자열 Type인 string에 대해 심도 깊게 다뤄보도록 하겠다. 

 

 

문자열(String)

  • Python에서 문자열은 ''(Quotation)나 ""(Double Quotation) 안에 문자를 넣어서 만든다.
  • Python의 기본 인코딩은 utf-8이므로, R과 달리 한글에 대한 텍스트 마이닝이 깔끔하게 잘 수행된다.
  • 문자열 데이터를 처리하여, 그 안에 숨어 있는 정보를 찾아내는 전반적인 과정을 텍스트 마이닝(Text Mining)이라 한다.

※ 텍스트마이닝(Text Mining)

  • 현장에서 접하는 대다수의 데이터는 숫자로 이루어진 연속형 데이터보다 Log Data 같은 문자, 사진 같은 이미지, 동영상, 음악과 같은 비정형 데이터가 대부분이다.
  • 대부분의 전처리(Data Handling)는 이러한 비정형 데이터를 분석에 용이한 숫자 데이터로 변환해주는 과정이다.
  • 문자형 Data를 연속형 데이터로 변환하는 경우, 크게 2가지 방법이 있다.
    1. 범주화: 문자에 해당하는 숫자를 배정 - 리소스를 덜 먹음
    2. 벡터화: One-Hot Vector, Word2Vec 등의 방법 사용 - 일반적으로 기계학습에서 사용

 

 

 

문자열 Data를 만들어보자

# 문자열 Data를 만들어보자.
# 1. "" or ''를 사용해서 만든다.
>>> str1 = "Python is very useful"
>>> print(str1)
Python is very useful

>>> str2 = 'Python is a lot of fun'
>>> print(str2)
Python is a lot of fun

>>> str3 = """Python is easy and fun"""
>>> print(str3)
Python is easy and fun

>>> str4 = '''So let's do Python'''
>>> print(str4)
So let's do Python
  • 문자열 Data는 ""나 ''를 사용해서 만들며, "나 ' 기호를 3번 연속 사용해서 만들 수도 있다.
  • 보통 """나 '''로 만드는 경우는 긴 문장이나 주석을 넣을 때 사용한다.
# 문자열 안에 문자열 생성 기호인 ''나 ""를 넣고 싶은 경우엔 어떻게 할까?
>>> print("Let's do our best today")
Let's do our best today

>>> print('Pororo said "Hello" to Crong')
Pororo said "Hello" to Crong

>>> Frozen = """
    Anna:
    "Elsa?  Do you want to build a snowman?
    "Come on let's go and play.
    I never see you anymore. Come out the door.
    It's like you've gone away.
    We used to be best buddies
    And now we're not. I wish you would tell me why.
    Do you want to build a snowman?
    It doesn't have to be a snowman."

    Elsa:
    "Go away, Anna."

    Anna:
    "...Okay bye."
    """
    
>>> print(Frozen)

Anna:
"Elsa?  Do you want to build a snowman?
"Come on let's go and play.
I never see you anymore. Come out the door.
It's like you've gone away.
We used to be best buddies
And now we're not. I wish you would tell me why.
Do you want to build a snowman?
It doesn't have to be a snowman."

Elsa:
"Go away, Anna."

Anna:
"...Okay bye."
  • 짧은 문장 안에 '나 "가 들어가야한다면 그에 상응하는 문자로 묶어주면 된다.
  • 그러나 이 두 가지가 모두 들어가야한다면, """ 문장 """으로 묶어주면 된다.
  • 또는 escape 문자인 \을 사용하면 된다.

 

 

 

이스케이프 문자(Escape Sequence)

파이썬을 포함한 각종 언어에서는 잘 사용하지 않는 특수 문자나 특수문자 + 문자 조합에 특정 기능을 넣어 놓는다. 그러나, 가끔 이 이스케이프 문자가 실제 문자 Data 대상인 경우도 있기 때문에 이스케이프 문자가 아님을 알려주는 방법을 숙지할 필요가 있다.

이스케이프 문자 이름 설명
\b 벡스페이스 뒤로 한 칸 삭제
\t 문자열 사이에 탭 간격 생성
\n 라인피드 줄 바꿈
\f 폼피드 현재 커서를 다음 줄로 이동
\r 캐리지 현재 커서를 가장 앞으로 이동
\\ 역슬래시 \를 이스케이프 문자 앞에 사용하는 경우 그대로 출력
  • 위 표에서 많이 사용되는 것은 탭, 라인피드, 이스케이프 문자 앞에 \ 사용하기이므로, 나머지 이스케이프 문자에 대해선 크게 신경 쓰지 말자.
  • 키보드에서 \는 존재하지 않는데, ₩ 키가 동일한 기능을 한다.
# 이스케이프 문자를 사용해보자
>>> str5 = "뽀로로: \"크롱 반가워!\"\n크롱:\"오! 뽀로로 좋은 아침이야!\""
>>> print(str5)
뽀로로: "크롱 반가워!"
크롱:"오! 뽀로로 좋은 아침이야!"

# 이스케이프 문자를 그대로 출력시켜보자
>>> str5 = "뽀로로: \"크롱 반가워!\"\\n크롱:\"오! 뽀로로 좋은 아침이야!\""
뽀로로: "크롱 반가워!"\n크롱:"오! 뽀로로 좋은 아침이야!"

 

  • 위 예제에서 역 슬래시 사용 후 "나 ' 같은 특정 기능이 들어간 특수 문자를 넣으면 그대로 출력되는 것을 알 수 있다.
  • 이스케이프 문자에서 \n나 \t는 Data를 line 단위로 가져오기, tap 단위로 구분 지어 가져오기(구분자)를 하는 경우에 문자 안에 해당 문자가 들어 있어, Row를 잘못 인식할 수도 있다(실제 Row의 수보다 더 많은 Row를 생성).
  • 텍스트 데이터를 다룰 때, 이스케이프 문자나 ' or "로 인해 의도치 않은 결과가 출력 될 수 있다.

 

 

 

 이번 포스트에서는 가볍게 문자열을 만드는 방법과 이스케이프 문자에 대해 알아보았다. 이스케이프 문자는 반드시 숙지하길 바라며, 최소한 \n이 내려쓰기, \t는 탭이라는 것은 꼭! 외워놓기 바란다.

 Data를 가지고 오는 방법 중 txt data를 line별로 가지고 오거나, 탭을 구분자로 가져오는 경우가 많은데, 대상 Data 안에 해당 이스케이프 문자가 들어 있다면(예를 들어 URL Log Data), 데이터를 잘못 불러오는 문제가 발생할 수 있다.

 다음 포스트에선 자동으로 문자열의 일부를 바꾸는 방법인 포맷팅(Formating)에 대해 학습해보겠다. 

 

728x90
반응형
728x90
반응형

 자, 드디어 마지막 기초 자료형인 DataFrame이다. DataFrame은 pandas의 대표적인 Type이며, R을 공부해 본 사람이라면, 상당히 친숙하게 느껴지는 단어일 것이다.

 pandas의 DataFrame은 R의 Dataframe을 Python에서도 사용해보기 위해 만들어졌으며, R에서 할 수 있는 대부분의 기능을 판다스에서도 구현할 수 있다.

 이번 포스트에서는 데이터 분석가들의 필수 Type인 DataFrame이 어떻게 생겼는지와 아주 대략적인 대표 기능만 살펴보고 바로 넘어가도록 하자.

 

 

 먼저 DataFrame을 만들어보자.

  • DataFrame을 만드는 방법은 크게 2가지가 있다.
    1.  길이가 동일한 list들을 컬럼 하나하나에 배정하는 방법
    2. M*N 행렬 형태의 Data(Array, Tupple)를 DataFrame에 넣는 방법
# pandas 모듈을 가지고 오자
>>> import pandas as pd
>>> import numpy as np


# 1. 길이가 동일한 list들을 DataFrame에 넣어보자
# DataFrame에 들어갈 길이가 같은 list들을 만들자
>>> name = ["민철", "기훈", "재성", "현택", "윤기"]
>>> math = [40, 60, 80, 75, 65]
>>> english = [75, 80, 65, 80, 70]
>>> science = [85, 70, 75, 80, 60]

# list들을 이용해서 DataFrame을 만드는 경우는 다음과 같다
>>> DF = pd.DataFrame({"name":name, "math":math, "english":english, "science":science})
>>> DF

name	math	english	science
0	민철	40	75	85
1	기훈	60	80	70
2	재성	80	65	75
3	현택	75	80	80
4	윤기	65	70	60



# 2. array를 이용해서 DataFrame을 만들어보자.
# 10, 100 사이의 임의의 값으로 만들어진 행렬을 생성하자
>>> row_number = 50
>>> score_mat = np.random.randint(10, 100, size=(row_number, 4))

# ID를 만들어보자
>>> ID_list = []
>>> for i in range(row_number):
    
	    ID = "A" + str(i)
    	ID_list.append(ID)
    
# shape을 맞춰서 ID_array와 score_mat을 병합시켜보자
>>> ID_array = np.array(ID_list)
>>> ID_array = ID_array.reshape((50, 1))

>>> data_array = np.hstack((ID_array, score_mat))

# array를 DataFrame을 만들어보자
>>> DF2 = pd.DataFrame(data_array, columns=["ID", "math", "English", "science", "Korean"])
# 생성한 DataFrame의 상위 10개만 출력해보자
>>> DF2.head(10)


ID	math	English	science	Korean
0	A0	35	38	62	51
1	A1	52	29	40	93
2	A2	28	16	99	71
3	A3	93	42	61	48
4	A4	23	60	39	48
5	A5	93	96	16	55
6	A6	13	69	88	90
7	A7	31	18	80	30
8	A8	59	12	66	93
9	A9	54	70	57	38
  • DataFrame을 처음 보면 마치 엑셀에서 우리가 일반적으로 만들던 표랑 굉장히 유사하다는 것을 알 수 있다.
  • DataFrame에는 각 열(Column)별로 동일한 데이터 타입을 넣을 수 있다.
  • pd.DataFrame() 함수를 통해 DataFrame을 만들 수 있다.
  • np.random.randint(시작 값, 끝 값, 형태) 함수는 시작 값, 끝 값 사이에서 랜덤한 값이 담긴 array를 생성한다.
  • np.hstack((array1, array2)) 함수는 두 array를 열을 기준으로 병합한다.
  • DataFrame.head(숫자) 함수는 내가 숫자만큼 DataFrame을 출력한다.

 

 

DataFrame의 컬럼별 dtype을 확인해보자

# DataFrame의 data type을 확인해보자
>>> DF2.dtypes
ID         object
math       object
English    object
science    object
Korean     object
dtype: object


# math, English, science, Korean 컬럼을 정수 type으로 바꿔보자
>>> DF2["math"] = DF2["math"].astype("int64")
>>> DF2["English"] = DF2["English"].astype("int64")
>>> DF2["science"] = DF2["science"].astype("int64")
>>> DF2["Korean"] = DF2["Korean"].astype("int64")
>>> DF2.dtypes
ID         object
math        int64
English     int64
science     int64
Korean      int64
dtype: object
  • DataFrame.dtypes 함수를 통해 DataFrame의 각 컬럼들의 Type을 확인할 수 있다.
  • DataFrame["컬럼"].astype("바꿀 dtype") 함수를 통해 DataFrame의 해당 컬럼 dtype을 바꿀 수 있다.

 

 

DataFrame을 Slicing 해보자.

# 10번 row부터 20번 row까지 출력해보자
>>> DF2[10:20]
	ID	math	English	science	Korean
10	A10	78	24	99	79
11	A11	41	87	83	10
12	A12	61	71	31	78
13	A13	74	80	32	99
14	A14	20	19	95	38
15	A15	24	67	22	24
16	A16	39	53	41	82
17	A17	34	57	52	67
18	A18	34	60	27	73
19	A19	27	35	91	81




# 수학 80점 이상인 Row만 출력해보자
>>> DF2[DF2["math"] >= 80]
	ID	math	English	science	Korean
2	A2	90	70	70	72
4	A4	90	27	64	42
8	A8	99	21	71	92
9	A9	89	61	11	30
28	A28	81	29	27	86
32	A32	84	19	89	31
44	A44	97	73	36	78
45	A45	80	95	54	12
48	A48	86	19	99	83
  • pandas의 dataFrame은 Numpy의 array와 동일한 방법으로 Slicing 할 수 있으며, 내가 원하는 조건에 대한 Row도 쉽게 가져올 수 있다.

 

 

 이번 포스트에서는 DataFrame에 대해 아주 간략하게 훑어만 봤는데, 설명을 보다 보면 설명이 지나치게 부족하지 않는가? 하는 생각이 들 것이다. 

 pandas의 DataFrame 역시 Numpy의 array와 마찬가지로 그 영역이 매우 크기 때문에 따로 카테고리를 만들어서 세세하게 설명하고자 한다.

 이번 포스트에서는 맛보기로 DataFrame이 어떻게 생겼는지만 인식하는 수준에서 끝내고, 추후 Python-pandas 카테고리의 포스트에서 pandas의 각 기능들을 세세하게 따져보도록 하겠다.

 
728x90
반응형
728x90
반응형

 이번 포스트에서는 Dictionary에 대해 살펴보도록 하겠다.

 

 

Dictionary

개요

  • Dictionary는 말 그대로 사전이라고 할 수 있으며, 사전은 기본적으로 "단어: 설명"의 구성으로 돼있다.
  • Python의 Dictionary 역시 key : value 관계로 이루어져 있으며, key를 이용해 해당하는 value를 받을 수 있다.
  • Dictionary의 안에는 scalar, list, array, DataFrame 등 다양한 Type의 Data 담을 수 있다.
  • Dictionary의 key는 순서가 따로 없으며, 내가 원하는 key에 해당하는 value를 얻고자 할 때, 순서가 없으므로 순차적으로 탐색하지 않고, 바로 value를 가지고 오므로 속도가 매우 빠르다
  • Dictionary의 안에 또 Dictionary를 넣을 수 있으며, 이 구조는 마치 Tree 구조와 같다.

 

 

 위 이야기만 보면 잘 와닿지 않으므로, 실제로 Dictionary를 만들어보자.

# Dictionary를 만들어보자.
>>> dict_a = {}
>>> type(dict_a)
dict

>>> dict_b = dict()
>>> type(dict_b)
dict
  • Dictionary는 위와 같이 dict()라는 함수를 쓰거나 {}를 이용해서 만들 수 있다.

 

 

 이번엔 Dictionary에 Data를 넣어보자

# Data를 넣어보자
>>> dict_c = {"아침":"만두", "점심":"햄버거", "저녁":"된장찌개"}
>>> dict_c
{'아침': '만두', '점심': '햄버거', '저녁': '된장찌개'}
  • Dictionary에는 key:value로 Data를 넣는다.
  • key에는 문자형("", ''로 묶인!), scalar(정수, 소수, None, Boolean)가 들어갈 수 있다.
  • value엔 무엇이든지 다 들어갈 수 있다.

 

 

 조금 더 복잡하게 Data를 넣어보자

>>> dict_c = {"아침":{14:"만두", 15:"피자", 16:"곰탕"},
              "점심":{14:"햄버거", 15:"떡볶이", 16:"라면"},
          	  "저녁":{14:"된장찌개", 15:"김치찌개", 16:"삼겹살"}}
>>> dict_c
{'아침': {14: '만두', 15: '피자', 16: '곰탕'},
 '점심': {14: '햄버거', 15: '떡볶이', 16: '라면'},
 '저녁': {14: '된장찌개', 15: '김치찌개', 16: '삼겹살'}}
  • dictionary 안에는 dictionary를 포함한 다양한 type의 data를 넣을 수 있다.

 

 

 야식이라는 key를 추가해보자

# "야식"이라는 새로운 Dictionary를 추가해보자
>>> dict_c["야식"] = {14:"김밥"}
>>> dict_c
{'아침': {14: '만두', 15: '피자', 16: '곰탕'},
 '점심': {14: '햄버거', 15: '떡볶이', 16: '라면'},
 '저녁': {14: '된장찌개', 15: '김치찌개', 16: '삼겹살'},
 '야식': {14: '김밥'}}
  • dict["key] = value를 사용하면 새로운 key와 value를 추가할 수 있다.

 

 

 이번에는 dict_c의 key가 무엇이 있는지 가져와보자

# Dictionary의 key들을 가지고 와보자
>>> dict_c.keys()
dict_keys(['아침', '점심', '저녁', '야식'])


# Dictionary 안의 "아침"dictionary의 key들을 가지고 와보자
>>> dict_c["아침"].keys()
dict_keys([14, 15, 16])


# key "아침"의 16일 식단이 무엇인지 알아보자
>>> dict_c["아침"][16]
'곰탕'


# key "야식"에 16일 식단을 추가해보자
>>> dict_c["야식"][16] = "짜파게티"
>>> dict_c
{'아침': {14: '만두', 15: '피자', 16: '곰탕'},
 '점심': {14: '햄버거', 15: '떡볶이', 16: '라면'},
 '저녁': {14: '된장찌개', 15: '김치찌개', 16: '삼겹살'},
 '야식': {14: '김밥', 16: '짜파게티'}}
 
 
# 알고보니 15일 저녁에 라볶이를 먹었다. 수정해보자
>>> dict_c["저녁"][15] = "라볶이"
>>> dict_c
{'아침': {14: '만두', 15: '피자', 16: '곰탕'},
 '점심': {14: '햄버거', 15: '떡볶이', 16: '라면'},
 '저녁': {14: '된장찌개', 15: '라볶이', 16: '삼겹살'},
 '야식': {14: '김밥', 16: '짜파게티'}}
  • Dictionary의 key나 value는 쉽게 추가, 수정할 수 있으며, 내가 원하는 것을 가져올 수 있다.

 

 

때로는 Dictionary를 이용해서 다양한 Data들을 담아놓는 거대한 선반 역할을 할 수도 있다.

# 기존에 배웠던 Type들을 모두 넣어보자
>>> import tensorflow as tf
>>> import numpy as np

>>> dict_d = {"scalar_1":int(32.9),
    	      "list_1":["A1", "A2", "A3", "A4"],
        	  "array_1":np.array([[1,3,5,7],[2,4,6,8]]),
          	  "tensor_1":tf.zeros(shape=(2, 3))}

>>> dict_d
{'scalar_1': 32,
 'list_1': ['A1', 'A2', 'A3', 'A4'],
 'array_1': array([[1, 3, 5, 7],
        [2, 4, 6, 8]]),
 'tensor_1': <tf.Tensor: shape=(2, 3), dtype=float32, numpy=
 array([[0., 0., 0.],
        [0., 0., 0.]], dtype=float32)>}
        
        
>>> dict_d["tensor_1"]
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[0., 0., 0.],
       [0., 0., 0.]], dtype=float32)>
  • Dictionary에는 다양한 종류의 Data를 넣어놓고 내가 필요한 Data를 꺼내 쓰는 곧간처럼 쓸 수 있다.
  • 말 그대로 Dictionary는 사전이므로, Tokenize 된 단어와 벡터들을 연결해서 word:Vector 사전으로 쓸 수도 있다.
  • Dictionary의 형태는 또 다른 자주 쓰이는 Type인 json과 동일하다. 그러므로, json을 사용하고자 하는 경우 Dictionary를 잘 다룰 수 있으면 매우 편하다.

 

 

Json

  • json은 데이터 분석을 위해서 다룰 줄 알아야 하는 Type 중 하나이지만, dictionary와 거의 유사하므로 따로 설명하지는 않곗다.
  • json은 Dictionary처럼 Data가 key:value로 이어진 형태이며, 따옴표(')와 큰따옴표(")로 발생하는 문제만 잘 해결한다면 Dictionary를 그대로 json으로 변환해서 사용해도 된다.
  • json은 json 모듈을 이용해 Python의 객체(Dictionary)를 Json 문자열로 변환시키면 된다.
  • 판다스의 DataFrame은 key(column: 열)과 value(row: 행)으로 서로 연결된 형태이므로, 사실 Dictionary와 굉장히 유사한 형태이다. 그러므로 Pandas의 DataFrame도 json 형태로 쉽게 만들 수 있다.
  • 추후 json module에 대해 설명하며 json에 대해 더 자세히 다루도록 하겠다.

 

 

 

 이번 포스트에서는 Dictionary에 대해 알아보았는데, 이번 포스트는 지금까지 포스트 중 가장 성의가 없지 않나?라는 생각이 들 정도로 글의 양이 적은 거 같다.

 다만, 위 함수들만 있어도 Dictionary를 사용하는데 큰 지장은 없으므로, 굳이 사족을 달지는 않겠다.

 다음 포스트에서는 데이터 분석가에게 익숙한 데이터 타입 중 하나인 DataFrame을 간략히 설명하고 기초 자료형에 대한 설명을 마치도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서 Numpy의 array에 대해 간략하게 훑어보았다. 이번 포스트에선 머신러닝에서 대표적으로 사용되는 모듈인 tensorflow의 고유 Type 중 하나인 Tensor에 대해 학습해보겠다.

 

 

tensor

개요

  • Tensorflow는 Google에서 개발한 머신러닝의 대표적인 모듈로 tensor는 이 tensorflow의 대표적인 Type이지만, 정작 Tensorflow를 이용해서 머신러닝을 할 때에는 사용할 일이 많지 않은 type이기도 하다.
  • "Tensorflow를 활용해서 기계학습을 진행하는데 tensor를 쓰지 않는다니??"라는 생각이 들 수 있는데, tensorflow로 기계학습 진행 시, 일반적으로 keras를 이용해서 model을 생성하게 되고, 그 모델에 들어갈 데이터셋의 핸들링 과정에서 tensor로 핸들링하는 것보다 Numpy의 array를 이용해서 핸들링할 일이 더 많기 때문이다.
  • 애초에 Tensorflow의 tensor와 Numpy의 array의 관련 함수의 상당수가 거의 유사하다 보니, 여기저기 많이 쓰여 익숙하게 쓰는 Numpy의 array가 더 편하기 때문이기도 하다.

 

 

Tensor란?

  • Tensor란 N 차원의 배열(array)을 의미한다.
  • 일반적으로 1차원 배열을 벡터(Vector), 2차원 배열을 행렬(Matrix), 3차원 이상의 배열을 다차원 배열이라고 하며, 이들 모두를 통틀어서 Tensor라고 한다.
  • Tensor는 N 차원 배열이므로, 배열을 다루는 Numpy의 array와 태생적으로 기능이 유사할 수 밖에 없다.

 

 

Rank

Rank 이름 내용
0 스칼라 원소 1개, Python-기초: 1.0. 자료형(1) - scalar 참조
1 벡터 1차원 array, Vt = [0.1, 0.2, 0.3]
2 행렬 2차원 array, Mt = [[1, 3, 5] , [2, 4, 6]]
3 3 차원 텐서 3차원 array - Channel 추가
n n 차원 텐서 n차원 array - 축이 n개인 array
  • 뭔가 대충 설명한 느낌이 강한데, 보다 구체적으로 이해하려면 머릿속에 그림을 그려봐야한다.
  • Scalar > Vector > Matrix > 3-Tensor 이런 식으로 하나하나 층을 쌓아보자

  • Scalar를 하나의 원소로 보고, 그 원소가 일렬로 쭉 쌓으면 Vector, 그 Vector를 아래로 쭉 쌓으면 Matrix, Matrix를 N개의 층을 쌓으면 N-Tensor가 되는 것이다.
  • 보통 3-Tensor는 그림을 Tensor화(수치화) 했을 때, Red, Green, Blue에 대한 3개의 Layer를 쌓는다. 이를 Channer이라 한다.

 

 

Tensor를 가지고 놀아보자

# Tensor를 생성해보자.
# 1. 0으로 채워진 Tensor를 만들어보자
>>> tf.zeros(shape=(3, 10))
<tf.Tensor: shape=(3, 10), dtype=float32, numpy=
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)>
       
       
# 2. 1로 채워진 Tensor를 만들어보자
>>> tf.ones(shape=(3,4), dtype = "int16")
<tf.Tensor: shape=(3, 4), dtype=int16, numpy=
array([[1, 1, 1, 1],
       [1, 1, 1, 1],
       [1, 1, 1, 1]], dtype=int16)>
       
       
# 3. 내가 원하는 상수로 채워진 Tensor를 만들어보자 
>>> tf.fill(dims=(3, 6), value=3)
<tf.Tensor: shape=(3, 6), dtype=int32, numpy=
array([[3, 3, 3, 3, 3, 3],
       [3, 3, 3, 3, 3, 3],
       [3, 3, 3, 3, 3, 3]])>
       
     
# 4. array를 만들듯이 Tensor를 만들어보자
>>> tf.constant([[1,3,5,7],[2,4,6,8]])
<tf.Tensor: shape=(2, 4), dtype=int32, numpy=
array([[1, 3, 5, 7],
       [2, 4, 6, 8]])>
       

# 5. array를 이용해서 Tensor를 만들어보자
>>> array1 = np.array([[1, 3, 5, 7], [2, 4, 6, 8]])
>>> tf.constant(array1)
<tf.Tensor: shape=(2, 4), dtype=int32, numpy=
array([[1, 3, 5, 7],
       [2, 4, 6, 8]])>


# 6. 패턴을 갖는 Tensor를 만들어보자
>>> tf.range(1, 20, 3)
<tf.Tensor: shape=(7,), dtype=int32, numpy=array([ 1,  4,  7, 10, 13, 16, 19])>
  • 별 설명 없이 여러 tensor를 만들어보았는데, 이를 보면 앞서 봤던 numpy의 array와 굉장히 유사하다는 생각이 들지 않는가?
  • 애초에 tf.constant() 함수에 array를 담으면 tensor가 되고 모든 형태의 array를 포함하는 것이 tensor이므로, 굳이 tensor를 공부하지 않아도 Numpy의 array만 잘 다루면 tensor를 무리 없이 쓸 수 있다.

 

# Tensor의 다양한 속성들을 뽑아보자
>>> TS1 = tf.constant([[1, 3, 5, 7, 9, 11], [2, 4, 6, 8, 10, 12], [2, 3, 5, 7, 11, 13]],
						dtype="float64")
>>> TS1
<tf.Tensor: shape=(3, 6), dtype=float64, numpy=
array([[ 1.,  3.,  5.,  7.,  9., 11.],
       [ 2.,  4.,  6.,  8., 10., 12.],
       [ 2.,  3.,  5.,  7., 11., 13.]])>
   
   
# 1. shape을 뽑아보자 
>>> TS1.shape
TensorShape([3, 6])


# 2. shape을 바꿔보자
>>> tf.reshape(TS1, shape=(2, 9))
<tf.Tensor: shape=(2, 9), dtype=float64, numpy=
array([[ 1.,  3.,  5.,  7.,  9., 11.,  2.,  4.,  6.],
       [ 8., 10., 12.,  2.,  3.,  5.,  7., 11., 13.]])>


# 3. dtype을 뽑아보자
>>> TS1.dtype
tf.float64


# 4. dtype을 바꿔보자
>>> tf.cast(TS1, dtype="int64")
<tf.Tensor: shape=(3, 6), dtype=int64, numpy=
array([[ 1,  3,  5,  7,  9, 11],
       [ 2,  4,  6,  8, 10, 12],
       [ 2,  3,  5,  7, 11, 13]], dtype=int64)>
  • 위 예제를 보면 굳이 tensor로 변환을 하지 않고, Numpy로 데이터 핸들링을 하는 것이 더 편하겠다는 생각이 들지 않는가?
  • tensor를 통해 사용할 수 있는 함수가 이외에도 수없이 많으나, 이에 시간을 할애하기보다는 scipy, pandas, matplotlib 등 다양한 모듈에서 폭넓게 사용할 수 있는 Numpy를 공부하는 것이 보다 효율적으로 판단되므로, 본 블로그에서는 tensor의 관련 함수는 여기까지만 정리하도록 하고 Numpy를 더 자세히 학습하도록 하겠다.

 

 

dtype

  • 위 예제를 보면 dtype="int64"와 같이 우리가 기존에 알고 있는 int 뒤에 숫자가 붙어 있는 것을 알 수 있다.
  • dtype은 float32, float64, int8, int16, int32, int64, unit8, string, bool 등이 있으며 뒤에 있는 숫자는 bit를 의미한다.
  • 종종 앞에 있는 dtype인 float은 일치하지만 bit가 일치하지 않아 오류가 발생하기도 하니, 혹여 dtype error가 뜨는 경우, 이를 확인해보도록 하자.

 

 

 

 지금까지 가볍게 tensor에 대해 학습해보았다. tensor는 Numpy의 array와 기능이 거의 일치하며, tensorflow로 학습을 할 때도 Numpy를 주로 사용하니 더 파고들진 않겠다.

 그러나 위에서 설명한 Tensor Rank 그림은 상당히 중요한 그림이므로, 꼭 숙지하도록 하자.

728x90
반응형
728x90
반응형

 지금까지 scalar, list Type에 대해서 알아보았다. 이제 남은 대표적인 Type은 array, tensor, dictionary, DataFrame이 있는데, 이들은 앞서 다뤘던 두 Type에 비해 훨씬 심도 깊은 학습이 필요하므로, 특징만 간략히 설명하고 넘어가겠다.

 

 

array

Numpy의 array 

  • Python의 단점을 이야기할 때, 흔히들 느린 속도를 꼽는데, 이 이미지를 한 번에 종식시킬 수 있는 것이 바로 Numpy라는 모듈이며, 그 Numpy 모듈의 기본 Type이 바로 array다.
  • Numpy는 C언어, Fortran을 기반으로 만들어졌기 때문에 연산 속도가 매우 빠르며, 쉬운 것이 장점인 파이썬으로 구현되어 있기에 C언어를 공부하지 않고도 복잡한 수학 연산 문제를 아주 쉽고 빠르게 접근할 수 있다.
  • 특히 데이터 분석가의 친구인 판다스의 단점인 느린 속도를 해결할 수 있기 때문에 데이터 분석을 하고자 한다면, Numpy를 아주 잘 다룰 수 있어야 한다.
  • Numpy의 array는 Scipy, tensorflow, sklearn, Matplotlib, Pandas 등 빅데이터 분석에서 필수로 사용되는 모듈을 활용하는 데에 있어 기초가 되어준다.
  • 보다 상세한 내용은 추후 Numpy에 대해 자세히 학습할 때 이야기해보도록 하자.
# array를 사용하기 위해선 numpy 모듈을 가지고 와야한다.
>>> import numpy as np

# array는 list를 만들고 np.array() 함수에 넣어서 생성하기도 한다.
>>> a = [1,2,3,4,5]
>>> a_array = np.array(a)
>>> a_array
array([1, 2, 3, 4, 5])
  • numpy 모듈을 불러오는 import numpy as np는 "numpy를 수입(import) 해오겠다 np 처럼(as)" 이라고 곧이곧대로 해석해도 된다. 
    1. 이게 파이썬의 대표적인 장점 중 하나로 꼽히는 파이썬 코드가 우리가 실제로 사용하는 언어와 굉장히 가깝다는 것을 보여주는 대표적인 예시중 하나이다.
  • np는 numpy의 줄임말이며, Python은 "."을 기준으로 하여, 모듈로부터 하위 함수로 한 단계 한단계 내려가는 형태를 가지고 있다.
    • 즉, np.array()는 numpy 모듈의 array() 함수라는 뜻이다.
# array에 담겨있는 data type(dtype)은 상당히 중요하다.
>>> a_array.dtype
dtype('int32')


# 소수(float)로 구성된 array를 만들어보자
>>> b = np.arange(1, 10, 2, dtype = "float")
>>> b
array([1., 3., 5., 7., 9.])

>>> b.dtype
dtype('float64')
  • array.dtype은 array의 data type을 알 수 있다.
  • np.arange()은 앞서 list의 range와 비슷한 기능을 하며, array를 바로 생성한다.
  • array 내 data를 정수로 입력했다 할지라도 dtype을 "float"으로 지정하면 소수로 생성된다.

 

 

array 연산과 Broadcast

# Numpy의 array 연산은 R의 Vector 연산과 굉장히 유사하다.
>>> c = np.array([1, 3, 5, 7, 9])
>>> d = np.array([2, 4, 6, 8, 10])
>>> e = np.array([10, 20])


# 자리수가 동일한 array(vector)끼리는 동일한 위치의 원소끼리 연산이 이루어진다.
>>> print(c+d) 
[ 3  7 11 15 19]
>>> print(c*d)
[ 2 12 30 56 90]
>>> print(c/d)
[0.5        0.75       0.83333333 0.875      0.9]


# 자리수가 동일하지 않은 array(vector)간의 연산은 이루어지지 않는다.
>>> print(c + e)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-12-3b9c09c64b20> in <module>
----> 1 print(c + e)

ValueError: operands could not be broadcast together with shapes (5,) (2,) 
  • Numpy의 array 연산을 보면 R의 Vector 연산과 꽤 유사한 것을 알 수 있다.
  • Numpy의 array는 Tensorflow의 기본 Type인 Tensor와 거의 동일한 개념으로 봐도 문제없다.
    1. Tensor란 다차원 배열(array)을 아우르는 말이며, 이 안에는 1차원 배열인 Vector와 2차원 배열인 Metrics를 포함하는 것이다.
    2. 선형 대수학의 관점에서 접근할 때, array에 보다 익숙해지기 위해 1차원 array는 단순하게 vector로 부르도록 하겠다.
  • shape(모양)이 동일하지 않은 array끼리 연산 시, ValueError가 발생한다.
# BroadCast
>>> c
array([1, 3, 5, 7, 9])

>>> c * 2
array([ 2,  6, 10, 14, 18])

>>> c + 2
array([ 3,  5,  7,  9, 11])

>>> c / 2
array([0.5, 1.5, 2.5, 3.5, 4.5])
  • 1차원 array에 scalar 값을 연산 시, array의 모든 원소에 scalar값이 연산된다.
# array를 여러개 쌓으면 행렬이 된다.
>>> f = np.array([[1, 3, 5, 7],[2, 4, 6, 8]])
>>> f * 2
array([[ 2,  6, 10, 14],
       [ 4,  8, 12, 16]])
 
 
# 행렬에 대한 Broadcast
>>> f = np.array([[1, 3, 5, 7],[2, 4, 6, 8]])
>>> f * np.array([10, 20, 30, 40])
array([[ 10,  60, 150, 280],
       [ 20,  80, 180, 320]])
  • m*n행렬에 대해 스칼라 값을 연산하거나  n*1 벡터를 연산하면 array의 Broadcast와 같은 방식으로 연산이 이루어진다.
#  행렬 곱
>>> f = np.array([[1, 3, 5, 7],[2, 4, 6, 8]])
>>> f * np.array([[10, 10, 10, 10], [20, 20, 20, 20]])
array([[ 10,  30,  50,  70],
       [ 40,  80, 120, 160]])
       

>>> g = np.array([[10,10],[20,20],[30,30],[40,40]])
>>> np.dot(f,g)
array([[500, 500],
       [600, 600]])
  • 형태(shape)가 동일한 행렬끼리 *+-/와 같은 연산 수행 시, 동일한 위치에 있는 원소끼리 곱해진다(이는 우리가 일반적으로 아는 행렬곱이 아니다.).
  • m*n행렬, n*o행렬과 같이 행렬곱이 가능한 대상을 np.dot(mat1, mat2)을 하는 경우 우리가 아는 행렬곱이 연산된다.

 

 

배열의 형태

# array의 shape
>>> vt1 = np.array([1,2,3,4])
>>> vt2 = np.arange(1, 5, 0.1)
>>> mat1 = np.array([[1,3,5,7],[2,4,6,8]])
>>> mat2 = np.array([[1,10],[2,20],[3,30],[4,40]])

>>> print(vt1.shape)
(4,)
>>> print(vt2.shape)
(40,)
>>> print(mat1.shape)
(2, 4)
>>> print(mat2.shape)
(4, 2)


# 형태 변환(reshape)
>>> vt1.reshape((4,1))
array([[1],
       [2],
       [3],
       [4]])
>>> mat2.reshape((2,4))
array([[ 1, 10,  2, 20],
       [ 3, 30,  4, 40]])
       
       
# 평활(Flatten)
>>> mat1
array([[1, 3, 5, 7],
       [2, 4, 6, 8]])
>>> mat1.flatten()
array([1, 3, 5, 7, 2, 4, 6, 8])

>>> mat2
array([[ 1, 10],
       [ 2, 20],
       [ 3, 30],
       [ 4, 40]])
>>> mat2.flatten()
array([ 1, 10,  2, 20,  3, 30,  4, 40])
  • 앞서 이야기했던 array의 shape은 Numpy를 다루는 데 있어 필수 사항이며, 나아가 딥러닝을 다루는데 주로 사용되는 모듈인 tensorflow를 사용할 때도 매우 중요하다.
  • 상당수의 연산 오류는 shape이나 dtype이 일치하지 않아 발생한다.
  • reshape() 함수를 이용해서 array의 형태를 바꿀 수 있다.
  • 우리가 일반적으로 2차원으로 아는 행렬은 flatten() 함수를 통해 수월하게 벡터화되며, 이는 텐서플로우 학습 시, 상당히 중요한 내용이다.
    1. 행렬이 1차원 배열로 변환 가능하다는 것은 우리가 아는 2차원으로 알고 있는 행렬은 사실 상 1차원임을 의미한다. 이에 대해선 추후 Numpy를 설명하면서 보다 자세히 짚고 넘어가도록 하겠다.

 

 

 Numpy의 핵심 Type인 array는 이를 공부하는 데만 해도 상당한 시간을 투자해야 하기 때문에 이번 포스트에서는 가장 기본적인 array의 성격들만 알아보았다.

 앞서 list를 학습할 때, 발생했던 상당 부분의 list의 한계점으로 여겨졌던 부분들은 array를 통해 대부분 해결 가능하다.

 또 다른 Type인 tensorflow의 tensor는 numpy의 array와 굉장히 유사하며, 함수에 약간의 차이가 있긴하나 거의 동일한 기능들이 존재한다. 또한, tensorflow 학습 시, keras를 위주로 쓰게 되며, 굳이 tensor로 연산하기보다 numpy의 array로 연산하는 것을 추천한다. 

  1. tensorflow에서 tensor를 다루는 함수는 numpy의 array를 다루는 함수와 기능이 거의 일치한다. 굳이 tensor를 다루는 함수를 공부하지 않아도 tensorflow를 다루는데 큰 지장이 없다.
  2. tensorflow에 대해 추후 학습하겠으나, tensorflow 2.0 version에 들어오며, keras를 주로 활용하여 딥러닝을 실시하게 될 것이다.
  3. 상세한 내용은 python-Numpy 카테고리에서 포스팅하도록 하겠다.
728x90
반응형
728x90
반응형

 이전 포스트에선 빅데이터 분석을 하며 눈에 익었던 Python Type이 무엇이 있는지와 그중 하나인 Scalar에 대해 간략하게 알아보았다. 이번 포스트에서는 list에 대해 이야기해보자.

 

 

list

# list는 Data를 담는 가장 기본적인 상자라고 생각하자
# list는 다음과 같은 방법으로 만들 수 있다.
# 1. [] 사용하기
>>> list1 = [1, 2, 3, 4, 5, 6, 7]
>>> print(list1)
[1, 2, 3, 4, 5, 6, 7]

# 2. list() 함수 사용하기
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • list는 파이썬에서 가장 일반적인 데이터를 담는 type이다.
  • scalar(int, float, None, Boolean)뿐만 아니라 문자형(str)도 담을 수 있다.
  • 연산에 특화된 Type이 아닌 데이터를 담는 기본 그릇이라고 생각하는 것이 이해하기 쉽다.
  • list는 list() 함수, [] 안에 넣고자하는 Data를 넣어서 만들 수 있다.

 

Range

# range는 무엇인가?
>>> print(range(10))
range(0, 10)

# for문을 사용하여 range의 인자들을 출력해보자
>>> for i in range(10):
>>>    print(i)
0
1
2
3
4
5
6
7
8
9

# range()함수는 range(시작, 끝, 간격)으로 값을 내가 원하는 때에 호출할 수 있는 함수다.
>>> [i for i in range(4, 20, 2)]
[4, 6, 8, 10, 12, 14, 16, 18]
  • range() 함수는 꽤 자주 쓰이는 함수로, list와 꽤 친하니 이참에 설명하고 가겠다.
  • range(시작 값, 끝 값, 간격)을 입력하여 내가 원하는 패턴을 갖는 임의의 데이터를 생성할 수 있다.
  • 중요사항!) list에 data를 담는 경우, 그 data를 전부 생성해버리기 때문에 list를 생성하는 순간부터 memory를 쭉 잡아먹지만, range() 함수는 내가 원할 때, 그 값을 가지고 오기 때문에 memory 낭비가 발생하지 않는다.
    1. range() 함수를 출력하는 경우 range()라는 객체가 반환된다.
      • 객체란? 아주 단순하게 설명해보자면, X라는 함수를 통해 무언가를 만들어냈으나, 그 결과물이 가지고 있는 정보가 매우 다양해서 한 번에 다 보는 것이 어려운 상태이다.
      • 이 결과물에 특정 스위치를 붙이고 스위치를 켜주면, 그에 해당하는 정보를 볼 수 있다.
      • 즉, "내가 보여줄 수 있는 것은 아주 많은데, 뭘 원해?"라고 물어보고 있는 상태라고 생각하면 쉽다.
    2.  range() 함수로 생성된 range 객체는 당장 값이 나오지 않으며, for문과 같은 기능을 실행해주어야, 값을 반환한다.
      • 내가 원할 때, 값을 가지고 온다(for문을 실행)는 것은 제네레이터(Generator)와 유사해보이지만, range()는 엄밀히 따지면 Genarator가 아니다.
      • next() 함수를 이용해서 값을 꺼내면 오류가 발생한다.
      • 이는 추후 제네레이터를 설명할 때 다시 언급하도록 하겠다.

 

 

 

list의 연산

>>> a = [1,2,3,4]
>>> b = [5,6,7]
>>> print(a + b)
[1, 2, 3, 4, 5, 6, 7]


>>> print(a * b)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-20-f9c8a447c001> in <module>
----> 1 print(a*b)

TypeError: cant multiply sequence by non-int of type "list"


>>> print(a * 3)
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
  • Data가 담기는 그릇인 list는 연산보다는 Data를 담는 것이 주목적이다 보니 우리가 생각하는 연산과 다른 결과가 출력된다.
  • list와 list를 더 하면 두 list의 값이 합쳐지는(sum)것이 아닌 한 그릇으로 합쳐진다(extend).
  • list와 list의 곱은 데이터가 담긴 두 그릇을 곱하는 것이니 연산되지 않는다.
  • list에 int를 곱하면 데이터가 담긴 그릇을 그 int만큼 더 추가한다.
  • 이는 list가 연산이 아닌 데이터를 담는 그릇의 성질이 더 강하다는 것을 뜻한다. 때문에 list에는 숫자형과 문자형을 함께 담을 수도 있으며, 함께 담는다고 해서 데이터의 타입이 자동으로 바뀌지 않는다.

 

 

 

list의 인덱싱(슬라이싱)

# 내가 원하는 위치에 있는 값을 가지고 올 수 있다.
>>> c = list(range(20))
>>> print(c[3])
3
>>> print(c[5:12])
[5, 6, 7, 8, 9, 10, 11]

# list의 안에는 다른 list를 담을수도 있다!
>>> d = [3,5,7,['a', 'p', 'p', 'l', 'e']]
>>> print(d[3])
['a', 'p', 'p', 'l', 'e']
>>> print(d[3][3:])
['l', 'e']
  • Data를 담는 그릇인 list는 아주 쉽게 내가 원하는 위치에 있는 데이터를 가지고 올 수 있다.
  • list[위치]를 이용하면 그 위치에 있는 값을 가지고 온다.
  • Data를 담는 그릇인 list는 list 안에 list를 담을 수 있는데, 안에 있는 list를 가지고 오고 그 list 안에서 원하는 위치를 지정하면 똑같이 가져올 수 있다.
  • :는 가져오려는 index의 구간을 정해줄 수 있다.
# list에서 특정 조건에 맞는 값을 가지고 오기.
>>> print(c)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]


# 1. list c에서 10 이상인 값들을 가지고 와보자.
>>> c >= 10
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-36-0a29ddccf71f> in <module>
----> 1 c >= 10

TypeError: '>=' not supported between instances of 'list' and 'int'


# 2. for문과 if문을 혼용하여 가져와보자
>>> result = []
>>> for i in c:
>>>     if i >= 10:
>>>         result.append(i)
>>>     
>>> print(result)
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

# 3. 위 코드보다 단순하게 적어보자
>>> [i for i in c if i >= 10]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
  • R이나 파이썬 numpy의 array는 c[c>=10] 처럼 단순한 코드로 조건에 해당하는 값을 가지고 올 수 있으나, list는 이 방법으로 data를 가지고 올 수 없다.
  • if문을 사용하여야하며, 아직 익숙하지 않은 사람이라면 "# 2. 주석"대로 코드를 짜는 것이 쉬우며, 어느 정도 코드에 익숙해진 후에는 "# 3. 주석"의 코드 같이 짧은 코드로 짜도록 하자.

 

 

 

기타 리스트에서 자주 쓰이는 함수

# 1. append: list에 값 추가
>>> e = []
>>> for i in range(2, 10, 2):
>>>     e.append(i)
>>>     
>>> print(e)
[2, 4, 6, 8]

>>> e0 = [1,3,5]
>>> e0.append([2,4,5])
>>> print(e0)
[1, 3, 5, [2, 4, 5]]




# 2. extend: list에 다른 list의 원소들을 추가하기
>>> e.extend([1,3,5,7])
>>> print(e)
[2, 4, 6, 8, 1, 3, 5, 7]




# 3. sort: 정렬
>>> e.sort(reverse = True)
>>> print(e)
[8, 7, 6, 5, 4, 3, 2, 1]

>>> e.sort()
>>> print(e)
[1, 2, 3, 4, 5, 6, 7, 8]




# 4. insert: 삽입
>>> e.insert(2, 20)
>>> print(e)
[1, 2, 20, 3, 4, 5, 6, 7, 8]




# 5. pop: 뽑아내기
>>> e1 = e.pop(2)
>>> print(e1)
20
>>> print(e)
[1, 2, 3, 4, 5, 6, 7, 8]




# 6. remove: 가장 왼쪽에 있는 해당하는 값 제거
>>> f = [4,5,6] * 3
>>> f.remove(5)
>>> print(f)
[4, 6, 4, 5, 6, 4, 5, 6]




# 7. len: list 내 원소의 갯수 세기
>>> print(len(f))
8




# 8. count: list 내 특정 원소의 갯수 세기
>>> print(f.count(5))
2
  • list.append(value)는 list에 다른 값(list도 하나의 원소로써 추가 가능!)을 추가하는 함수다.
  • list.extend(list)는 list + list랑 같다
  • list.sort(reverse = False)는 자주 쓰이는 함수인데, reverse는 역순으로 정렬하느냐를 의미한다. 기본적으로 reverse = False로 지정돼있으며, 이는 오름차순(작은 순)이다.
  • 그냥 list의 순서를 거꾸로 하고 싶다면 list.reverse()를 쓰자
  • list.insert(위치, 값) 함수를 이용하면 내가 원하는 위치에 원하는 값을 넣을 수 있다.
  • list.pop(위치) 함수는 해당 위치에 있는 value를 아예 뽑아버린다!
  • list.remove(값) 함수는 가장 왼쪽에 있는 해당 값만 제거하므로 모두 제거하고 싶다면 for문이나 while문으로 반복실행하자
  • len(list) 함수는 list 내 원소의 수를 가지고 온다.
  • len(문자) 함수는 문자의 길이를 반환한다.
  • list.count(value)는 list 내 value의 개수를 가져오는 유용한 함수다.

 

 지금까지 list type에 대해 빠르게 훑어봤는데, 파이썬의 가장 기초가 되는 Data를 담는 type이다 보니 꽤 자주 쓰이는 type이다.

 위의 list 관련 함수들을 보다 보면, 분석가가 바라는 기능에 비해 조금씩 나사가 빠져있어, 원하는 결과를 내기 위해선 for문이나 while문을 섞어줘야 하는 경우가 꽤 있다.

 이 것이 본문에서 "list는 Data를 담는 그릇이지, 연산을 위한 Type은 아니다"라 한 이유이기도 한데, 이후에 학습할 array, DataFrame 등은 연산이나 데이터 정리에 훨씬 특화돼있기 때문에 연산이나 Indexing 등에서 훨씬 쉽고 우월한 성능을 낸다.

 이후 포스트에서 다룰 array와 DataFrame 등은 그 기능이 굉장히 다양하고, 이를 설명하려면 어지간한 전공서적만큼 설명이 필요하므로, 나머지 Type 포스팅에선 살짝 찍어만 먹어보고, 다른 카테고리인 Python-Pandas와 Python-Numpy에서 자세히 다뤄보자.

728x90
반응형
728x90
반응형

 

 Python, R과 같은 수많은 언어들에서 제일 중요한 건, 해당 언어에서 어떠한 자료형을 가지고 있느냐다.

 대부분의 오류는 사용하려는 api에 데이터를 담고 있는 형태(Type), 담겨 있는 데이터의 형태(Data type), 데이터의 구조(Shape)가 적합하지 않아 발생하며, 어지간해선 이를 해결하면 해결된다.

 성능향상 문제에서도 Type, Data type, Shape은 지대한 영향을 미치며, 이를 어떻게 가지고 노느냐에 따라서 같은 알고리즘도 전혀 다른 방식으로 접근할 수 있다.

 그러므로, Python 첫 번째 page에선 이에 대해 간략하게 살펴보도록 하자

  • Python을 사용하면서 느낀 중요한 부분에 대해 짚고 넘어갈 것이므로, 보다 상세한 설명을 원한다면 구글링 하길 바란다(분석가에게 제일 중요한 능력은 검색 능력이지 않는가!).
  • 일부 자료형은 설명하다보면 아주 깊게 들어가게 되므로, 자세한 설명은 뒤로 미루도록 하겠다.
  • Python을 사용하며, 데이터 분석에 유용하다고 느낀 Type을 위주로 설명한다.

 

 

 

Type

  1. scalar
  2. list
  3. array
  4. tensor
  5. dictionary
  6. DataFrame

 위 6가지 외에도 여러 Type이 존재하지만, 데이터 분석을 하면서, 숨 쉬듯 사용했던 Type 위주로 추려냈다.

 위 Type중 numpy의 array나 pandas의 DataFrame, tensorflow의 tensor 같이 해당 Type의 기능이 꽤 많아 제대로 이해하고 사용하려면 두꺼운 책 여러 권을 읽어야 하지만, 이는 뒤로 미뤄두고 오늘은 가볍게 설명해보자.

 

 

 

Scalar

 scalar를 극단적으로 단순하게 말해보면, 그냥 숫자나 문자가 딱 한 개 나오는 경우를 가리킨다. 여기서 문자라는 것은 눈에 보이기엔 문자처럼 보이지만 실제로는 숫자인데, 대체 이게 뭔소리냐 싶겠지만, 설명을 들어보면  꽤 쉽게 이해될 것이다.

 Python의 scalar에는 크게 4개의 자료형이 있다.

  1. int
  2. float
  3. None
  4. Boolean

 여기서 int는 정수, float는 소수, None은 값이 없음, Boolean은 True or False이다. 자, 이제 하나하나 설명해보도록 하자.

 

int(정수)

# int
>>> 1
1
  • 자, 숫자 1을 셀 안에 넣고 ctrl + enter를 입력하니 1이 반환되었다.
  • >>>는 입력한 코드
  • 아래에 >>> 없이 숫자만 나온 경우는 출력된 결과이다.
>>> a = 1
>>> print(a)
1
>>> print(type(a))
<class 'int'>
  • a에 1을 할당하고 print() 해보자
  • print()는 말 그대로 print(출력)하는 함수이다.
  • 참고로 주피터 노트북의 경우 셀의 맨 마지막 줄에 출력하고자 하는 함수를 입력하면, 해당 함수의 내용이 바로 출력 된다.
  • type()은 변수의 자료형을 확인하는 방법이다.
# int 함수를 이용하면 소수를 정수로 바꿀 수 있다.
# 소수 > 정수
>>> int(2.35)
2
# Boolean > 정수
>>> int(True)
1
# 문자 > 정수
>>> int("123")
123
  • int() 함수는 int가 될 수 있는 자료형을 int로 바꿔주는 역할을 한다.
  • ""나 ''는 문자를 의미하는데 담겨 있는 내용이 숫자인 경우엔 숫자로 바꿀 수 있다.
  • True의 경우 분명 문자인데 정수 1로 바뀌는 것이 이상하게 보일 수 있는데, 이는 꽤 재미난 성질이므로, 뒤에서 다시 언급하도록 하겠다.
  • 그러나, 정수로 형태를 변환시킬 수 없는 경우 ValueError가 뜨게 된다.
>>> int("scalar")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-17-45517d963ca8> in <module>
----> 1 int("scalar")

ValueError: invalid literal for int() with base 10: 'scalar'



>>> int("1.25")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-18-fb02bea94e38> in <module>
----> 1 int("1.25")

ValueError: invalid literal for int() with base 10: '1.25'



>>> int("True")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-19-c0bc75205372> in <module>
----> 1 int("True")

ValueError: invalid literal for int() with base 10: 'True'
  • "" 나 '' 안에 문자 혹은 소수가 들어가는 경우 ValueError가 발생한다.
  • 앞에서 1로 바뀌었던 True 역시 "True"로 들어가는 경우 ValueError가 발생한다.

 

 

float(소수)

# float
>>> print(1.25)
1.25

>>> b = 2.75
>>> print(b)
2.75

>>> print(type(b))
<class 'float'>

>>> print(float(10))
10.0
  • 말 그대로 소수이다.
  • int() 함수와 마찬가지로 float()를 하면 정수를 소수로 치환할 수 있다.
  • int()함수와 마찬가지로 float()이 될 수 없는 대상을 치환하려고 하는 경우 ValueError가 뜬다.
# 반올림, 올림, 버림을 해보자
>>> import numpy as np
>>> import math

>>> c = 15.62719

# 반올림
>>>print(np.round(c, 2))
>>>print(round(c, 3))
15.63
15.627

# 올림
>>> print(np.ceil(c))
>>> print(math.ceil(c))
16.0
16

# 버림
>>> print(np.trunc(c))
>>> print(math.trunc(c))
15.0
15
  • 반올림은 python의 기본 함수인 round가 있다.
  • 반올림, 올림, 내림은 주로 numpy, math 모듈의 함수를 이용한다.
  • numpy으로 충분하므로, 굳이 math 모듈을 가지고 올 필요는 없다.
  • import는 특정 모듈을 가지고 온다는 것을 의미하며, as는 그 모듈을 as 뒤에 있는 문자로 간단하게 표현하겠다는 것을 의미한다.
  • np.round(x, N): float x를 소수점 N 자리에서 반올림한다.
  • np.ceil(x): float x를 올림 한다(math.ceil()과 달리 결과는 float으로 반환).
  • np.trunc(x): float x를 내림한다(math.trunc()과 달리 결과는 float으로 반환).

 

 

None(값없음)

>>> None

>>> d = None
>>> print(d)
None
  • 말 그대로 값이 없다는 것을 의미한다.
  • 특정 변수를 미리 생성해놓고 추후 Data를 넣는 경우나, 결측 값 같이 값이 있어야 하는데 존재하지 않음을 나타낼 때 사용된다.

 

 

Boolean

>>> e = 10
>>> print(e == 10)
True

>>> print(e == 12)
False

>>> print(e != 10)
False

>>> if e == 10:
>>>     print("Go")
>>> else:
>>>     print("stop")

Go
  • Boolean 또는 Bool이라 불리는 scalar는 True와 False 두 개의 값으로 이루어져 있다.
  • if 문과 같이 True, False에 따라 다른 결과를 반환하는 경우나, 특정 값을 추출할 때 자주 사용된다.
>>> bool(1)
True

>>> bool(10.0)
True

>>> bool("abc")
True

>>> bool(0)
False

>>> bool(0.0)
False

>>> bool("")
False

>>> print(int(True))
1

>>>> print(int(False))
0
  • Boolean의 True는 1에 False는 0에 대응한다(이 성질을 잘 이용하면 코드를 아주 단순하게 만들 수 있다).
  • bool() 함수를 이용하면 0, 0.0, ""은 False에 나머지 경우는 True를 반환한다.

 

 

그 외

 위 내용을 보면 그렇다면 알파벳 같은 문자의 Type은 무엇일까? 하는 궁금증이 들 것이다.

>>> type("abc")
str

>>> type("123")
str
  • str이라 반환되며, 이는 string의 줄임말로 문자열을 의미한다. 문자와 숫자 Data는 그 접근 방향이 꽤나 다르므로, "Data가 딱 하나 있는 것들을 scalar라고 뭉뚱그려 말하지 않는구나!"라고 단순히 생각해보자.
  • scalar에 대한 심층적인 설명으로 들어가면, 내용이 상당히 길어지므로, 단순하게 숫자로 표현할 수 있거나 값이 존재하지 않는 경우를 scalar라고 생각하면 된다(상세한 내용은 위키피디아를 참조하자).
  • scalar는 뒤에서 배울 벡터에 원소가 하나만 있는 상태라고 생각하면 된다.

 

 

 이번 포스팅에서는 데이터의 Type과 scalar에 대해 알아보았다. 간략히 설명하려 했으나, 쓰다 보니 내용이 꽤 길어져 이 사람이 뭘 이야기하고 싶은 건가 싶을 수도 있으니, 다음 포스트부터는 좀 더 단순하게 적어보도록 하겠다.

 
728x90
반응형
728x90
반응형

 요즘 일 때문에 많이 바빴더니 몇 일째 포스트 하지 못했다. 퇴근 후에 취미 생활을 즐기기 위해선, 운동을 하긴 해야겠다.

 이전 포스트에선 csv 파일을 가지고 오는 방법과 인코딩 문제에 대해 다뤄보았다. R을 사용하기 까다롭게 만드는 문제 중 하나가 인코딩 문제이고, 반면에 파이썬은 UTF-8을 기반으로 작성했기 때문에, 위 문제에서 보다 자유로운 편이다.

 R을 사용하다보면, 분석 환경 세팅 난이도는 그리 높지 않은 편인데(오프라인 환경이나 보안이 강한 환경에서도 큰 문제없이 설치가 가능하다), 데이터를 Import(가져올 때), Export(내보낼 때) 문제가 종종 발생한다. 이 문제는 대부분 인코딩 문제 거나, 데이터 자체에 R로 Import 될 때, 문제를 일으킬 수 있는 인자가 있기 때문이다.
(예를 들어 \n과 같은 문자는 줄을 바꾸는 문자인데, 이런 특수한 역할을 하는 문자가 섞인 데이터에선 데이터의 형태가 변형되어 버릴 수 있다. 이 문제는 R에만 국한된 것은 아니지만, 이 기회에 살짝 언급하고 가겠다.)

  이번 포스트에선 R로 엑셀 파일을 가지고 오고 내보내는 방법에 대해 학습해보도록 하겠다.

 

 

엑셀 파일을 R에서 불러와보자.

  • R에서 엑셀 파일을 불러오기 위해선 readxl 패키지를 사용해야한다.
  • read_excel()
    :  엑셀 파일을 읽어오는 함수
  • 주요 parameter
    : read_excel("파일위치/파일이름.확장자", range = "A1:B3": 엑셀 시트 내에서 내가 불러오고자 하는 영역, col_names = TRUE: 첫 행이 컬럼의 이름인지, sheet = 1, 불러올 시트)
    • read_excel의 특징은 자기가 원하는 sheet를 불러올 수 있다는 것이다.
    • read_excel은 Excel의 특징인 시트 내 좌표를 이용해서 내가 원하는 구간만 데이터를 가져올 수 있다. 
library("readxl")
read_excel(file.choose(), col_names = TRUE, range = "A1:D6", sheet = 1)
  • file 이름(워크 스페이스가 기본 경로이며, 다른 경로를 쓰고 사용하고 싶은 경우엔 해당 경로를 추가하면 된다.)
    이번 포스트에선 file.choose()를 넣어보았다.

  • file.choose()를 매개변수로 넣게 되면, 우리에게 익숙한 파일을 불러오는 창을 볼 수 있다.
  • file.choose()는 R에 익숙할수록 딱히 필요 없는 기능이지만, 파일의 경로에 대해 익숙하지 않은 초보에겐 추천하는 기능이다.
    (일단 파일을 불러와야 뭘 해볼 것 아닌가!)
## # A tibble: 5 x 4
##   name  class  math English
##   <chr> <dbl> <dbl>   <dbl>
## 1 사나      1    80      70
## 2 다연      1    85      65
## 3 쯔위      1    70      90
## 4 모모      1    65      60
## 5 예림      2    80      70

 

 

겸사겸사 워크 스페이스 아래에서 경로를 가지고 장난을 쳐보자.

  • 워크스페이스 경로는 D 드라이브의 RWorkSpace 파일이다.
  • 워크스페이스 경로에 손대지 말고, 해당 파일 안에 있는 Data 파일의 exam_exl.xlsx 파일을 불러와보자.

 

library("readxl")
getwd()
## [1] "D:/RWorkSpace"
read_excel("./Data/exam_exl.xlsx", col_names = TRUE, sheet = 1)
  • ./Data 에서 .은 워크스페이스의 경로(정확히는 현 위치)를 의미한다.
## # A tibble: 15 x 6
##    name  class  math English Korean science
##    <chr> <dbl> <dbl>   <dbl>  <dbl>   <dbl>
##  1 사나      1    80      70     65      90
##  2 다연      1    85      65     75      70
##  3 쯔위      1    70      90     65      60
##  4 모모      1    65      60     55      70
##  5 예림      2    80      70     65      55
##  6 현희      2    75      85     60      80
##  7 정은      2    90      60     70      85
##  8 혜원      2    65      80     75      80
##  9 준식      2    75      70     90      65
## 10 재성      3    70      90     65      85
## 11 민철      3    60      85     75      90
## 12 현택      3    75      65     80      70
## 13 현승      3    90      70     65      85
## 14 윤기      3    80      65     90      70
## 15 기훈      3    95      55     85      90
  • 위 방식을 이용하면, 파일 관리를 보다 수월하게 할 수 있다.

 

 

 

 

엑셀 파일로 저장해보자.

  • 엑셀로 파일을 불러왔으니, 이번엔 엑셀로 파일을 내보 내보자.
  • 위에서 사용한 패키지인 readxl은 말 그대로 엑셀을 읽어오는 패키지이기 때문에, 엑셀로 파일을 내보내려면, writexl이라는 패키지를 설치해야 한다.
  • 그러나 가능한 데이터는 엑셀 파일로 저장하지는 말도록 하자(데이터가 엑셀로 와서 어쩔 수 없는 상태에 엑셀을 R로 읽는 것이지, 데이터 관리를 엑셀로 하는 것은 좋지 않다. 이에 대한 내용은 후술 하도록 하겠다.)
  • write_xlsx()
    : 데이터를 엑셀 형태로 저장한다.
  • 주요 parameter
    : write_xlsx(data, path = "경로/파일이름.xlsx")
library("writexl")
data = data.frame(ID = c("A01", "A02", "A03", "A04", "A05"),
                  math = c(60, 80, 70, 90, 65))

write_xlsx(data, path = "./Data/test.xlsx")
  • 위 과정을 거치면 아래와 같이 엑셀 파일이 생성되는 것을 확인할 수 있다.

 

 

 

 

엑셀 파일로 데이터 관리를 하는 것은 좋을까?

 이번 포스트에선 엑셀로 파일을 올리고 내리는 법을 알아보았다. 그런데 과연 엑셀로 파일을 관리할 일이 있을까? 엑셀은 데이터를 관리하는 데 있어서 상당히 많이 사용되지만, 동시에 단점도 많은 툴이기 때문에, 개인적으로 비추한다. 엑셀을 사용할 바에 csv 파일로 파일을 읽고 쓰는 것을 추천한다.

 엑셀의 단점은 다음과 같다.

  • 엑셀에 단위가 매우 큰 숫자를 넣는 경우 E(지수 형태)로 변환이 되어, 실제 값의 정보가 손실된다.

  • 많은 양의 데이터(행이 65,536개 이상)를 관리할 수 없다.

 물론, 위 문제가 R에서 패키지를 이용해서 파일을 불러오고 내보내는 상황에서도 적용되는지 확신은 못하지만, 당신이 R에 익숙해지면 익숙해질수록, 엑셀은 느려 터지고, 불편한 툴이라는 생각이 들어서 자동으로 멀리하게 될 것이다. R은 코드를 통해서 작동하므로, 엑셀보다는 진입장벽이 있긴 하지만, R에 익숙해지고 나면, 속도, 기능 등 모든 면에서 R이 압도적이므로, 엑셀로 파일을 관리하는 일은 사라지게 될 것이다.
(필자는 엑셀로 수정할 바에 차라리 R로 코딩을 하는 것을 선호할 정도로 R은 쉽고 빠르다!)

 

 

 이번 포스트에선 엑셀로 R에 파일을 올리고 내리는 방법에 대해 학습해보았다. 다음 포스트에선 텍스트 파일을 R로 다루는 방법에 대해 학습해보겠다.

728x90
반응형
728x90
반응형

 요 근래 정보 보안 관련하여, 새로운 프로젝트에 참가하게 되었다. 기계 학습을 활용해서 해킹 시도를 막는 알고리즘을 손보는 것인데, 데이터 분석은 꽤 다뤄봤기에 부담스러울 것 없지만, 보안에 대한 지식은 전무한 상태라 제대로 된 접근이 힘들 것이다. 그런고로 이번 포스트에선 정보 보안이 어떠한 형태로 돌아가는지 기초적인 수준의 개념을 알아보고자 한다.

※ 본 포스트의 목적은 정보 관제에 대한 기초 상식을 파악이 목적으로, 심도 깊은 수준까지의 탐색은 하지 않도록 하겠다.

 이번 포스트에서는 와우북스 보안 시리즈 중 하나인 "빅데이터 분석으로 살펴본 IDS와 보안 관제의 완성 - 패턴 매치 기법을 이용한 정보보안 감시체계의 완성 - 강명훈 저"라는 책을 바탕으로 작성하였다.

 

정보 보안(Information Security 또는 Inforsec)

먼저 정보 보안에 대해서 위키피디아가 어떻게 설명하고 있는지를 봐보자.

https://ko.wikipedia.org/wiki/%EC%A0%95%EB%B3%B4_%EB%B3%B4%EC%95%88

 

정보 보안 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 정보 보안(情報保安, 영어: information security 또는 inforsec, 정보 보호)는 정보를 여러가지 위협으로부터 보호하는 것을 뜻한다. 정보의 수집, 가공, 저장, 검색, 송�

ko.wikipedia.org

 

  • 위키피디아의 내용을 읽어보면, 정보 보안이란, "정보의 유통 과정에서 훼손, 변조, 유출되는 것을 막는 것을 의미한다."라고 볼 수 있다.
  • 이를 보다 직설적으로 표현해보면, 소중한 "정보"를 누군가가 바꿔버리거나 훔쳐가려는 시도를 막는 행위라고 할 수 있겠다.
  • 정보 보안에는 3가지 주요 목표가 있다.
    1. 기밀성(Confidentiality)
      : 허락되지 않은 사용자가 정보의 내용을 알 수 없게 해야 한다.
    2. 무결성(Integrity)
      : 허락되지 않은 사용자는 정보를 마음대로 수정할 수 없다.
    3. 가용성(Availability)
      : 허락된 사용자가 정보에 접근할 때, 방해받아선 안된다.
      DDoS 공격(서비스 거부 공격: 특정 사이트에 엄청난 트래픽을 발생시켜서 정상적인 사용자가 사용을 못하도록 만드는 행위)이 좋은 예시임.

 자, 간단하게 정보 보안이 뭔지는 알았으니, 이번에는 이 정보보안을 위해서 이루어지는 보안관제(Manged Security Service)에 대해 알아보자.

 

보안관제(Managed Security Service)란?

: 국가 전산망 보안관제 지침에선 보안관제를 다음과 같이 정의하고 있다.
"정보통신망을 대상으로 수행되는 사이버 공격정보를 탐지, 분석, 대응하는 일련의 활동"

  • 보안관제란 말 그대로, 정보 보안을 위해, 공격이 들어오는 것을 탐지하고, 공격에 대해 분석하고, 이에 대응하는 모든 활동을 가리킨다.
  • 즉, 해킹을 막기 위한 일련의 활동이다.

이번에는 이 보안관제에 사용되고 있는 기법들에 대해서 알아보자.

 

 

 

 

 

 

보안솔루션

방화벽(Fire Wall)

  • 네트워크 구성 시 당연히 설치되어야 하는, 필수이자 기본 솔루션이다.
  • 방화벽의 룰은 아주 단순하다. 사전에 허용하기로 정의한 IP와 Port가 아니면 차단한다.
  • 방화벽은 패킷 또는 세션 단위로 모든 트래픽의 허용 또는 차단 로그를 남긴다.
  • 사고가 난 경우, 이를 분석할 때, 큰 도움이 되지 않는다.
    1. 방화벽은 최소 예방 보안으로, 역할이 끝난다.
    2. 방화벽에 대한 로그 데이터는 패킷 또는 세션 단위로 모든 트래픽에 대하여 허용, 차단 로그가 남으므로, 그 양이 어마어마하다.
    3. 방화벽을 통과해서 보안 사고가 발생한 경우, 얻을 수 있는 정보는 접근 시간대 정도이다.
    4. 그러므로, 공격 패턴이나 트래픽 발생 양상과 같은, 방화벽보다 공격임이 뚜렷한 로그에 대해서만 사고 분석을 실시하는 것이 옳다.

 

 

IDS/IPS

  • IDS/IPS는 TCP/IP 최상위 계층인 응용계층까지 검사 가능한 최초의 보안솔루션으로, 현존하는 보안솔루션 중 네트워크를 통해 침입하는 보안 위협을 그나마 가장 효과적으로 감시할 수 있는 룰 기반 보안솔루션이다.
    • 참고 서적 저자의 견해
      : 네트워크를 통해 전달되는 데이터는 최종적으로 인간이 식별 가능한 문자나 숫자 등의 '기호'로 표현되는 정보를 송수신하면서 상호 간 서로 의도했던 의미를 수립한다.

       데이터는 인간의 의사소통과 마찬가지로 표현에 사용된 기호의 성격에 따라 최종 전달되는 정보의 의미가 달라지며, 최종 전달되는 정보의 보안 위협 여부 판단을 위해, 문자열 패턴(Signature)을 이용하여, 패킷 데이터 영역에 사용된 기호를 검사하는 방식은 매우 효과적이고, 그 이상 향상된 방식은 개발되지 않았다.
      (물론 이 방식은 커다란 허점을 가지고 있음)
  • IDS와 IPS의 핵심 기능은 사전에 정의된 룰과 트래픽의 비교를 통해 보안 위협을 찾아내는 것이다.
  • 즉, 둘 다 탐지가 핵심 기능이다(그러므로, 둘 중 무엇이 더 우월하다고 판단해서는 안된다).
  • IDS와 IPS는 같은 룰 운영치계를 가지고 있다.
  • IP 주소, Port 번호뿐만 아니라, TCP/IP 응용계층의 데이터까지 검사 가능(패턴 매치 기법 or DPI)
    • 간단하게 말하자면, 방화벽은 편지지의 봉투 주소만 확인하는 것이고, IDS/IPS는 편지의 내용까지 검사한다
    • IDS/IPS는 문자열 패턴(Signature)을 검사할 수 있는 '룰 옵션' 구조를 이용해 패킷의 데이터(Payload) 영역까지 검사한다.
  • 네트워크 보안을 전체적으로 아우르는 주력 솔루션이다.
  • IDS/IPS는 '패턴 매치 기법'을 이용하는 '룰 기반 보안솔루션'이므로, 보안관제 업무의 핵심이며, 탐지와 방어라는 측면에서 서로 상호 보완하는 업무 프로세스를 가져야 한다.
    • IPS가 방어를 위한 탐지를 선행하며, 뒤에 IDS가 붙는다.
    • IPS만 단독으로 사용하는 경우, 부정확한 룰의 방어 기능에 의한 서비스 실패 등 많은 제약이 발생하며, 룰의 정확성을 향상하기 위해선 많은 데이터를 이용한 정확한 분석이 필요하나, 대량의 데이터를 정확히 분석 가능한 IPS는 사실상 없다.

IDS

  • IDS는 원본 트래픽을 손실이나 변조 없이 복사해주는 방비인 TAP이 기계적으로 복사해주는 트래픽을 검사하는 구조다.
  • 즉, 본래의 트래픽 유통에 전혀 관여하지 않는다.
  • Out of Path(또는 Mirror) 방식이라고도 한다.
  • 공격자에 대한 소극적인 방어만 가능(공격자 호스트로 리셋 패킷을 보냄, 방화벽과 연동해서 공격자 차단)

IPS

  • 기존 트래픽 유통에 직접 관여한다.
  • 모든 트래픽은 IPS를 거쳐야만 유통 가능하다.
  • InLine 방식이라고 한다.
  • 장애에 대비한 트래픽 우회(Bypass)가 가능한 TAP를 내장시키는 게 일반적이다.
  • 공격자에 대한 적극적인 방어 가능(공격자의 패킷을 Drop 함)
  • 트래픽 유통에 직접 관여하므로, 빠른 처리 속도가 필요하다(IPS의 트래픽 처리 성능에 문제가 있어서 트래픽 유통에 문제가 생긴다면, 당연히 사용자들이 싫어한다.).
  • 적극적인 방어가 가능한 IDS를 IPS라고 할 수 있다.

IDS/IPS 로그 구조

  • 탐지 시간부터 피해자 포트까지를 기본정보
  • 공격 패킷 데이터를 상세정보(Rawdata)라고 한다.
  • 상세정보는 바로 보여주진 않고, 별도의 조작을 통해서만 상세정보를 확인할 수 있다.

 

 

 

안티(Anti) DDos

  • DDoS(분산 서비스 거부 공격)
    : 시스템 및 네트워크 자원(CPU, 메모리, 디스크, 네트워크 대역폭, 세션 등)을 비정상적으로 소진시켜, 정상적인 자원 사용을 방해하는 DoS의 진화된 공격형태이며, 공격/피해자 분포가 일대일(1:1)인 DoS에 비해, 다대일(N:1)이라는 특징을 가지고 있다.
    • 아주 간단하게 말하면, 공격자가 상대방의 컴퓨터 자원을 크게 소모시키는 행위를 하여, 정상적인 행동을 못하게 만드는 것이다.
  • 평상시, 세션 발생 추이를 기준으로 한 비교 분석을 하여 DDoS 대비
  • 세분화될수록 분류와 분석은 복잡해지고 대응하기 어려워지므로, TCP 레벨의 DDoS 공격으로 통합해 접근
  • DDoS 대응을 위한 업무 프로세스
    1. DDoS 보호 대상 시스템 및 네트워크 선정
    2. 시스템 및 네트워크 자원(CPU, 메모리, 세션, BPS, PPS 등)의 사용 임계치 산정을 통한 DDoS 대응 장비 설정 최적화
    3. DDoS 공격 전담 관제를 통한 자원 사용 이상 징후 파악

 

 

 

웹 방화벽

  • 웹(웹서버, 웹 사용자 모두)은 공격자들의 최대 공격 목표이나, 방화벽, IDS/IPS, 안티 DDoS 솔루션들은 웹을 향한, 또는 웹을 통한 공격에 효과적으로 대응하지 못한다.
    • 방화벽, IDS/IPS, 안티 DDpS 모두 웹을 대상으로 하였을 때, 눈에 띌 정도로 큰 한계점이 존재함
  • 웹 방화벽의 접근제어 방식은 크게 두 갈레로 Positive 방식, Negative 방식으로 나눠진다.
분류 Positive Negative
개념 사전에 안전하다고 정의된 패턴만 허용 사전에 위험하다고 정의된 패턴만 거부(탐지/차단)
장점 알려진 정상 패턴만을 허용함으로써 알려지지 않은 공격까지 자동 차단 공격 패턴이 정확하면 서비스 구조 변경 등과 관계없이 적용 가능
단점 서비스 구조 등의 변경 발생 시 즉각적인 정책 반영 필요 알려지지 않은 공격 패턴에 관한 지속적인 연구 필요
적용 방화벽, 웹 방화벽 등
> 사전 정의된 패턴과 일치하지 않는 모든 트래픽 차단
IDS, IPS 등
> 사전 정의된 패턴과 일치하지 않는 모든 트래픽 허용
  • 웹 방화벽의 효과를 극대화시키기 위해선, 정상적인 웹사이트 이용 패턴을 사전에 정의해야 하며, 이 작업은 웹서버를 직접 구축하고 웹사이트를 직접 설계한 개발자 수준의 운영자가 있어야만 가능하다 할 정도로 고도의 숙련된 기술이 필요하다.
  • 이런 운영상 어려움으로 인해, 이론적으론 효과적이나 현실적으로는 그리 큰 효과를 보지 못한다.
  • 현재는 Positive, Negative 접근제어 방식을 혼용한 형태가 대세로, 기본적인 Positive 정책을 적용하고, 그 정책을 통과한 트래픽에 대해 IDS나 IPS처럼 공격 패턴을 탐지 또는 차단하는 Negative 정책을 적용하는 것이다.
  • 최근엔 평판(Reputation, 입소문 마케팅 기법을 적용해 다른 웹 방화벽의 공격 평가를 자신의 평가에 반영) 기법을 도입하려는 움직임도 있으나, 걸음마 수준이며, 이 또한 정확하게 평가된(정확하게 탐지 또는 차단한) 정보들이 모여야 한다.
  • Positive 또는 Negative를 막론하고 정확한 룰 운영이 먼저이다.

 

 

 

ESM(Enterprise Security Management)

  • ESM은 다른 보안솔루션에서 수집한 정보에 대한 관리와 분석의 '통합'을 기치로 하여 탄생하였다.
  • ESM의 핵심 목표는 다음과 같다.
    1. 흩어져 있는 보안솔루션 로그, 일반 시스템 로그를 수집하고, 체계적으로 이를 분류하여 통합 관리 및 분석을 시행함
    2. 체계적인 통합 분석을 통해 솔루션별 보안정책의 일관성 보장
  • ESM을 통해 통합관리를 하게 되면, 통합된 로그 간 연관성을 추적해서 공격 여부 판별의 신속성과 정확성을 높여주는 '연관분석'을 수행할 수 있다.
    1. 공격 시도가 발생 했을 때, 문제가 없는 것은 '허용 로그', 공격 실패는 '차단 로그', 공격 성공 가능성이 있는 '탐지 로그'로 로그는 나뉘는 데, 이 중 가장 신속하게 해결해야하는 문제는 '탐지 로그'이다.
    2. ESM의 연관분석 기능을 활용하면, 로그 성격과 임계치 설정을 통해 보안관제 업무를 공격 성공 가능성이 높은 탐지 로그 분석에 집중시킬 수 있다.

 

 

 

기타 보안 솔루션

  • 앞서 소개한 보안솔루션들은 네트워크 길목을 지키는 상시적 감시가 필요한 관문으로써 역할을 한다면, 해당 항목에서 소개할 솔루션들은 실시간 모니터링이 어렵거나 불필요, 또는 감시보다 예방 보안의 성격이 강하다.

 

1. 안티 스팸

  • 최소 예방을 해주는 안티 스팸은, 악의적인 이메일 유입을 최소화해주는 기능이다.
  • 예방 솔루션으로써 유용하지만, 최소 예방 보안이므로, 보안관제 업무에서 적극적으로 활용되는 수준은 아니다.

2. 유해 사이트 차단

  • 유해 사이트를 차단해주는 최소 예방 솔루션으로, 웹 트래픽에서 URL 또는 URI를 검사 후, 등록된 유해 사이트 리스트와 패턴이 일치하는 트래픽을 차단하는 것이다.
  • IDS/IPS 등의 분석 결과를 반영하여, 업무 공조와 함께 유해 사이트 데이터베이스를 업데이트하는 경우도 있다.
  • 비교적 단순하여, 방화벽, 웹 방화벽 등에서 기능을 제공하는 경우도 많다.
  • 안티 스팸처럼 최소 예방 보안이므로, 보안관제 업무에 적극 활용되지 않는다.

3. 서버보안

  • 서버보안은, 보안성 강화를 최우선 목표로 설계된 보안 운영체제(Secure OS)로, 군사 분야에 사용하기 위한 목적으로 개발된 아주 강력한 성능을 가진 보안 설루션이다.
  • 서버보안 솔루션은 강력한 접근통제를 통해 공격 행위 자체를 무력화시키며, 공격 시대 행위나 그 행위의 결과를 감시/차단하는데 주력하는 대부분의 보안솔루션과 결을 달리하는 솔루션이다.
  • 서버보안 솔루션은 시스템 최고 관리자인 root 사용자는 물론, 모든 프로세스를 필요한 만큼의 범위에서만 동작하도록 제한할 수 있으며, 이를 통해 보안에 어긋나는 부적절한 사용 시도를 사전에 차단할 수 있다.
  • 효율성이 낮고, 어렵다는 단점 때문에 정보보안 분야에서 활용 비중이 그리 크지 않다.

4. 안티바이러스

  • 안티바이러스는 네트워크에 설치되는 관문 보안 형태도 있긴 하지만, 대부분 엔드포인트(Endpoint), 즉 PC 보안 성격이 강하므로, 보안관제 업무에서 차지하는 비중이 매우 미미하다.
  • 최근에는 악성코드, 좀비 PC 방지 등의 목적으로 특화되면서 네트워크 보안솔루션으로 성장이 진행 중이다.

5. 정보유출 방지

  • 크게 DRM, DLP로 두 가지 솔루션으로 분류된다.
    1. DRM(Digital Rights Management)
      : 허가된 사용자만이 정보에 접근할 수 있도록, 정보 자체에 적용되는 일종의 암호화 기술
    2. DLP(Data Loss Prevention)
      : 네트워크 기반의 응용 프로그램(웹, 메일, 데이터베이스 등) 송수신 정보 통제(감시 및 차단), USB/CD-Writer 등의 미디어, 무선/블루투스 등 통신매체 통제
  • 웹, 메일, 메신저, P2P 등은 데이터의 양이 어마어마하므로 실시간 모니터링이 사실상 불가능에 가깝다.
  • 주로 사고 발생에 대비한 증거 확보 차원의 감사 목적으로 활용된다.

6. TMS(Threat Management System)

  • 위협 관리 시스템(TMS)은 IDS/IPS 등에서 수집된 탐지/차단 로그의 발생량과 룰(로그) 별로 산정된 위험도의 조합에 의해 위협 수준(정상/관심/주의/경계/심각)을 결정해주는 솔루션이다.
  • 룰 별 위험도 산정 기준은 다음과 같다.
위험도 산정 기준
보안 취약점 + 공격 코드 + 피해 사례가 발견된 경우
보안 취약점 + 공격 코드가 발견된 경우
보안 취약점이 발견된 경우
  • TMS는 IDS/IPS의 탐지/차단 로그를 기준으로 현재 발생하고 있는 위협 수준을 측정해주는 시스템이다.
  • 만약 IDS/IPS의 운영이 정상화되지 않는 경우 TMS는 무용지물로 전락할 가능성이 있다(ESM도 그렇다).

7. RMS(Risk Management System)

  • 위험관리 시스템(RMS)은 보호대상 정보자산의 구성 현황을 파악하고 자산별 운영체제, 애플리케이션, 가동 서비스 및 이와 관련된 취약점 현황을 파악 및 분석한 후, 이를 데이터베이스 화하여 위험 발생 요인의 체계적 관리를 목적으로 하는 시스템이다.
  • RMS는 취약점의 이력 추적 및 관리 용도에 적합하며, 제거가 힘든 취약점에 대한 보안관제 측면의 접근이 이루어질 수 있도록 하는 업무적 협조가 필요하다.

 

 

요약

  • 방화벽: 보안솔루션이라기보다 기본적인 보안 인프라에 가깝다.
  • IDS/IPS: 전체 네트워크 범위를 감당한다.
  • 웹 방화벽: 웹 보안에 특화돼 있다.
  • 안티 DDoS: DDoS 보안에 특화돼 있다.
  • ESM: 위 보안솔루션들의 로그를 통합 관리하는 핵심 보안관제 솔루션이다.
  • 이들 솔루션은 모두 정도의 차이는 있으나 '패턴 매치' 기반으로 동작하는 기능이 존재한다.
  • 보안솔루션 분야에서 '패턴 매치 기법'은 매우 중요한 비중을 차지하고 있다.

 

 

 지금까지 정보보안에 대한 기본 개념과 보안관제, 보안솔루션에 대해 간략하게 알아보았다. 만약, 보다 자세한 내용을 알고 싶다면, 본 포스트에서 참고한 서적인 "빅데이터 분석으로 살펴본 IDS와 보안 관제의 완성 - 패턴 매치 기법을 이용한 정보보안 감시체계의 완성 - 강명훈 저"를 읽어보길 바란다.

 다음 포스트에선 IDS에 대해 보다 자세히 다뤄볼 예정이다.

728x90
반응형

+ Recent posts