728x90
반응형

이전 포스트까지 정규 표현식이 무엇인지, Python에서 정규 표현식을 어떻게 탐색하는지에 대해 알아보았다. 이번 포스트에서는 가장 중요한 "메타 문자로 정규 표현식 패턴 만들기"에 대해 알아보도록 하겠다.

 

 

 

1. 메타 문자로 정규 표현식 패턴 만들기

  • 메타 문자로 정규 표현식 만들기는 크게 3가지 방법으로 돌아간다.
  1. 패턴 문자열 생성: 찾고자 하는 패턴 문자열(문자 클래스)을 생성한다.
  2. 패턴 문자의 반복 지정: 패턴 문자열이 몇 번 반복되는지 지정하기
  3. 패턴 문자의 위치 지정: 패턴 문자열로 시작하거나 패턴 문자열로 끝나는지 지정한다.
  • 위 3개 과정은 다 함께 진행될 수도 있고, 하나만 진행될 수도 있다. 이 외에도 문자의 패턴들을 그룹으로 만들거나 |(or)를 이용하여, 원하는 패턴이 존재하는 경우를 선택하게 할 수도 있다.
  • 각 부분에 해당하는 메타 문자는 이전 포스트인 <Python-기초:3.0. 정규 표현식(1) - 소개: 정규 표현식과 메타 문자>를 보면 깔끔하게 표로 정리해놨으니, 이를 참고하기 바란다.

 

 

 

 

2. 패턴 문자열 생성 하기

2.1. 문자 클래스 [ ]

  • 문자 클래스(character class)라고 하며, [ ] 안에는 1개 이상의 문자를 넣을 수 있고, 그 문자 중 하나라도 일치한다면 일치하는 패턴으로 판단한다.
# [abc]라는 정규 표현식 패턴 생성
>>> pattern = "[abc]"
>>> regex_pattern = re.compile(pattern)

>>> Text_cat = "cat"
>>> print(regex_pattern.findall(Text_cat))
<re.Match object; span=(0, 1), match='c'>


>>> print(regex_pattern.findall(Text_cat))
['c', 'a']
  • "[abc]"는 3개의 문자가 들어 있는 것으로 보이지만, [ ]는 문자 하나로 인식한다.
  • cat은 "[abc]" 내부의 c와 일치하므로, match 되는 값인 'c'를 반환하였다.
  • findall() 메서드로 탐색 시, 일치하는 패턴인 ["c", "a"]를 하나하나 반환하였다.

 

 

2.2. 문자 클래스 [ ]와 범위 특수 문자 "-"

  • [ ] 안에 들어간 특수문자는 메타 문자가 아닌 원본 문자의 의미로 사용된다.
  • 그러나, "-"와 "^"는 별개의 의미를 갖는다.
  • "-"는 문자 클래스 내에서 범위를 의미한다.
>>> pattern = "[0-9]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "I have 10 Cats"
>>> print(regex_pattern.findall(Text_data))
['1', '0']
  • "[0-9]"는 0에서 9까지의 숫자를 의미한다.
>>> pattern = "[a-z]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "I have 10 Cats"
>>> print(regex_pattern.findall(Text_data))
['h', 'a', 'v', 'e', 'a', 't', 's']
  • "[a-z]"는 a부터 z까지의 영문자, 즉 소문자를 의미한다.
>>> pattern = "[A-Z]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "I have 10 Cats"
>>> print(regex_pattern.findall(Text_data))
['I', 'C']
  • [A-Z]는 A부터 Z까지의 영문자, 즉 대문자를 의미한다.
>>> pattern = "[a-z]"
>>> regex_pattern = re.compile(pattern, re.IGNORECASE)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
  • re.compile() 안에 re.IGNORECASE(re.I)를 넣으면 대소문자 구분 없이 사용할 수 있다.
>>> pattern = "[a-zA-Z0-9]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['H', 'o', 'r', 'm', 'e', 'l', 'F', 'o', 'o', 'd', 's', '1', '0']
  • "[a-z]", "[A-Z]", "[0-9]"는 한 문자 클래스 안에서 동시에 사용될 수 있다.
>>> pattern = "[가-힣]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['나', '는', '미', '국', '의', '의', '스', '팸', '통', '조', '림', '개', '를', '가', '지', '고', '있', '다']
  • "[가-힣]"을 쓰면 한글에 대해서도 정규 표현식을 사용할 수 있다.
>>> pattern = "[가\-힣]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다. - 힣힣"
>>> print(regex_pattern.findall(Text_data))
['가', '-', '힣', '힣']
  • "\-"처럼 특수 문자 앞에 역슬래시를 사용하면 본래 문자를 그대로 쓸 수 있다.

 

 

2.3. 문자 클래스 [ ]와 반대 특수 문자 "^"

  • 문자 클래스 [ ] 안에 "^"가 들어가면, 그 내용이 반대가 된다.
>>> pattern = "[^가-힣]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' ', ' ', 'H', 'o', 'r', 'm', 'e', 'l', ' ', 'F', 'o', 'o', 'd', 's', ' ', ' ', ' ', '1', '0', ' ', ' ', '.']
  • 한글을 제외한 모든 문자열이 반환된 것을 볼 수 있다.
  • 공백(" ")도 하나의 문자이며, 이는 문자 클래스 안에 띄어쓰기로 표현할 수 있다.
>>> pattern = "[^가-힣 ]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['H', 'o', 'r', 'm', 'e', 'l', 'F', 'o', 'o', 'd', 's', '1', '0', '.']
>>> pattern = "[\^a-z]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['o', 'r', 'm', 'e', 'l', 'o', 'o', 'd', 's']
  • "-"와 마찬가지로 앞에 역슬래시를 붙여 "\^"로 만들면 문자열 자체로 사용할 수 있다.

 

 

2.4. 별도 표기법을 가진 문자 클래스

  • 앞서 학습한 문자 클래스 중 "[a-z]", "[A-Z]", "[0-9]", "[ ]"는 꽤나 빈번하게 사용되는 문자 클래스기 때문에 이를 별도의 표기법으로도 제공한다.
  • 대표적인 문자 클래스는 6가지로 다음과 같다.
문자 클래스 기능 유사한 기능을 하는 문자 클래스
\d 숫자 [0-9]
\D 숫자가 아닌 것 [^0-9]
\s 공백 문자(공백은 단순 띄어쓰기부터 내려쓰기, 탭 등을 포함) [ \t\n\r\f\v]
\S 공백 문자가 아닌 것 [^ \t\n\r\f\v]
\w 문자 + 숫자 + 언더바("_") [a-zA-Z0-9_]
\W 문자 + 숫자 + 언더바("_")가 아닌 것 [^a-zA-Z0-9_]
>>> pattern = "\W"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '.']
  • 위에서 소개한 별도 표기법을 가진 문자 클래스는 [ ] 안에 담을 수도, 따로 사용할 수도 있다.
>>> pattern = "\d"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['1', '0']


>>> pattern = "[\d]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['1', '0']
  • 한 문자 클래스에 대해선 별 의미가 없으나, 두 문자 클래스를 동시에 사용할 땐 크게 다르다.
>>> pattern = "\s\d"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' 1']
  • "\s\d"는 공백 뒤에 숫자가 오는 패턴이므로 '통조림 10개를'에서 ' 1'에 매치된다.
>>> pattern = "[\s\d]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' ', ' ', ' ', ' ', ' ', ' ', '1', '0', ' ', ' ']
  • "[\s\d]"는 공백과 숫자를 함께 넣은 문자 클래스 "[ 0-9]"와 같으므로, 다른 결과를 반환한다.

 

 

2.5. 모든 문자를 의미하는 Dot "."

  • "."은 모든 문자열 한자리를 의미한다.
  • 단 "." 은 줄 바꿈 문자 "\n"는 제외한다.
>>> pattern = "\d."
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['10']
  • 숫자 뒤에 문자가 하나 오는 경우인 10을 가지고 왔다.
>>> pattern = ".\d."
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' 10']
  • 공백도 Dot에 해당하므로 ' 10'을 가지고 왔다.
>>> pattern = "\d..."
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['10개를']
  • 추천하는 방법은 아니지만 Dot를 여러 개 붙여서 문자열의 길이도 지정할 수 있다.
  • Dot(".")은 기본적으로 줄 바꿈 문자인 "\n"을 제외하지만, re.DOTALL(re.S) 파라미터를 추가함으로써 줄 바꿈 문자도 포함할 수 있다.
>>> pattern = "\d..."
>>> regex_pattern = re.compile(pattern, re.DOTALL)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개\n 20개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['10개\n', '20개를']

 

 

 

 

3. 패턴 문자열의 반복 지정

  • 앞서 패턴 문자열을 만드는 법을 배워봤다. 문자 클래스(Character class)는 기본적으로 한 자리이므로, 사용자가 원하는 패턴을 찾아내려면 패턴 문자열의 반복 수를 지정해주면 된다.
  • 반복 수를 지정하는 방법은 다음과 같다.
메타 문자 의미
+ 앞의 패턴 문자열이 1번 이상 반복되어야 한다.
* 앞의 패턴 문자열이 0번 이상 반복되어야 한다.
? 앞의 패턴 문자열은 존재할 수도 있고, 없을 수도 있다(0 이상 1 이하).
{n, m} 앞의 패턴 문자열은 n번 이상 m번 이하 반복되어야 한다.
{n, } 앞의 패턴 문자열은 n번 이상 반복되어야 한다.
{n} 앞의 패턴 문자열을 n번 반복되어야 한다.
  • 앞서 학습한 패턴 문자열 작성 방식과 위의 반복 수를 이용해서 문장 내 원하는 패턴을 찾아보도록 사자.
  • 다음과 같은 문장이 있다고 가정해보자.
>>> Sentence = "마동석씨의 핸드폰 번호는 010-1234-5678, 이메일 주소는 Email_ID.1234@naver.com이다."
  • 여기서 핸드폰 번호를 추출해보자.
  • 핸드폰 번호는 01[016789]-[0-9]{4}-[0-9]{4}의 패턴을 갖는다.
>>> pattern = "01[016789]-[0-9]{4}-[0-9]{4}"
>>> regex_pattern = re.compile(pattern)

>>> print(regex_pattern.findall(Sentence))
['010-1234-5678']
  • 아주 쉽게 원하는 패턴을 찾아내었다.
  • 이번에는 이메일 주소를 추출해보자.
  • 이메일 주소의 패턴은 [a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]?[a-z]? 이다.
>>> pattern = "[a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]?[a-z]?"
>>> regex_pattern = re.compile(pattern)

>>> print(regex_pattern.findall(Sentence))
['Email_ID.1234@naver.com']
  • 이메일 주소의 패턴에는 "?"가 들어갔는데, 이메일 주소의 도메인은 아래와 같이 존재하는 경우도 있기 때문이다.
>>> Sentence = "나문희씨의 이메일 주소는 Email_ID@company.co.kr이다."

>>> pattern = "[a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]?[a-z]?"
>>> regex_pattern = re.compile(pattern)

>>> print(regex_pattern.findall(Sentence))
['Email_ID@company.co.k']
  • 정규 표현식의 첫 포스트에서 제시한 아래 예제에서 휴대전화 번호, 집 전화번호, 이메일 주소 들을 따로 추출해보자.
data = [
    "010.1234.5678", "010-1234-5678", "02-1234-5678", "031-123-4567", "042.987.6543",
    "emailID01@naver.com", "emailID02@gmail.com", "Email.03@daum.net", "EM_example.01@gmail.com",
    "email_1234@company.co.kr"
]
>>> pattern = "[a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]*[a-z]*"
>>> regex_pattern = re.compile(pattern)

>>> print(regex_pattern.findall(data))

  • re 모듈은 기본적으로 하나의 텍스트 데이터에 대해 동작하므로, for문을 이용해서 하나하나씩 비교하고, 해당하는 list에 담아보도록 하자.
# Data가 담길 기본 list를 생성한다.
cellphone_list = []
homephone_list = []
email_list = []


# 각 카테고리별 패턴
cellPhone_pattern = re.compile("01[016789][\-.][0-9]{4}[\-.][0-9]{4}")
homePhone_pattern = re.compile("0[2-6][1-5]?[\-.][0-9]{3,4}[\-.][0-9]{4}")
email_pattern = re.compile("[a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]*[a-z]*")


for target in data:
    
    cellPhone_Object = cellPhone_pattern.match(target)
    homePhone_Object = homePhone_pattern.match(target)
    email_Object = email_pattern.match(target)
    
    if cellPhone_Object != None:
        
        cellphone_list.append(cellPhone_Object.group())
        
    if homePhone_Object != None:
        
        homephone_list.append(homePhone_Object.group())
        
    if email_Object != None:
        
        email_list.append(email_Object.group())
>>> print(f"휴대전화 번호: {cellphone_list}")
>>> print(f"휴대전화 번호: {homephone_list}")
>>> print(f"휴대전화 번호: {email_list}")
휴대전화 번호: ['010.1234.5678', '010-1234-5678']
휴대전화 번호: ['02-1234-5678', '031-123-4567', '042.987.6543']
휴대전화 번호: ['emailID01@naver.com', 'emailID02@gmail.com', 'Email.03@daum.net', 'EM_example.01@gmail.com', 'email_1234@company.co.kr']
  • 앞서 학습한 패턴 문자열 생성과 패턴 문자열의 반복 횟수만 지정해주니 아주 간단하게 원하는 패턴만 추출하는 데 성공하였다.

 

 

 

 

4. 패턴 문자열의 위치 지정

  • 패턴 문자열이 맨 뒤에 있는지, 맨 앞에 있는지만을 지정할 수 있다.
  • '^': 패턴 문자열 앞에 위치하며, 텍스트의 맨 왼쪽이 해당 패턴 문자열로 시작해야 한다.
  • '$': 패턴 문자열 뒤에 위치하며, 텍스트의 맨 오른쪽이 해당 패턴 문자열로 끝나야 한다.
  • 아래와 같은 이름 데이터가 있다고 가정해보자.
>>> name = "김영수, 김나연, 최영호, 김영식, 박순자, 최정훈, 박성훈, 김상훈, 최현정, 박성민"

>>> name_pattern = re.compile("^김..")
>>> print(name_pattern.findall(name))
['김영수']


>>> name_pattern = re.compile("^최..")
>>> name_pattern.findall(name)
[]


>>> name_pattern = re.compile("박..$")
>>> name_pattern.findall(name)
['박성민']


>>> name_pattern = re.compile("김..$")
>>> name_pattern.findall(name)
[]
  • '^', '$' 패턴은 문자열의 맨 처음과 맨 끝만 중요하게 판단한다.
  • 성이 김으로 시작하는 사람을 찾고 싶다면, 물론 간단한 패턴 문자열 생성으로 해결할 수야 있다.
>>> name_pattern = re.compile("[ ]?김..")
>>> name_pattern.findall(name)
['김영수', ' 김나연', ' 김영식', ' 김상훈']
  • 그러나 간단한 몇 개의 조작만 한다면, '^', '$' 메타 문자만을 이용해서도 찾을 수 있다.
  • 먼저 각 이름의 구분자인 ', '를 줄 바꿈 문자인 '\n'으로 바꿔주자.
>>> name_LF = re.sub(", ", "\n", name)
>>> name_LF
'김영수\n김나연\n최영호\n김영식\n박순자\n최정훈\n박성훈\n김상훈\n최현정\n박성민'
  • re.sub(pattern, repl, string): string에서 pattern에 해당하는 문자열을 repl 문자열로 치환한다.
  • re.compile에 추가 파라미터 re.MULTILINE(re.M)를 하나 넣어줘 보자.
>>> name_pattern = re.compile("^김..", re.MULTILINE)
>>> name_pattern.findall(name_LF)
['김영수', '김나연', '김영식', '김상훈']
  • re.MULTILINE은 문자열 전체의 처음이나 끝이 아닌 각 라인의 처음이나 끝으로 설정해주는 파라미터이다.
  • 즉, '^'와 '$'를 각 줄에 적용할 수 있다는 것이다.
  • 이름이 "훈"으로 끝나는 사람도 쉽게 찾을 수 있다.
>>> name_pattern = re.compile("..훈$", re.MULTILINE)
>>> name_pattern.findall(name_LF)
['최정훈', '박성훈', '김상훈']
  • 이 외에도 "\A", "\Z"가 존재하는데, 각각 "^", "$"와 동일한 기능을 갖는다. 유일한 차이라면, re.MULTILINE 파라미터가 있어도 전체에 대해서 작동한다는 것이다.

 

 

 

 

5. 기타 메타 문자

  • 앞서 소개한 메타 문자 중 "|"와 "()" 이 두 가지를 아직 다루지 않았는데, "|"만 다루고, "()"는 다음 포스팅에서 다루도록 하겠다.
  • "()"는 Grouping이라는 것으로, 꽤나 복잡한 녀석이라 다음 포스트에서 자세히 설명해보도록 하겠다. 이번 포스트에선 "|"까지만 다루겠다.

 

5.1. "|": 또는(Or)

  • "A|B" 패턴 문자열이 있다면, A에 해당하는 패턴 문자열이나 B에 해당하는 패턴 문자열에 대해 가지고 온다.
>>> Text_data = "Banana, Apple, WaterMelon, Melon, StrawBerry"

>>> pattern = "Apple|Banana"
>>> regex_pattern = re.compile(pattern)
>>> print(regex_pattern.findall(Text_data))
['Banana', 'Apple']

 

 

 

지금까지 기본적인 정규 표현식 문법 작성 방법에 대해 알아보았다. 지금까지 다룬 내용만으로도 정규 표현식을 사용하는 것은 크게 어렵지 않으나, 혹시나 정규 표현식에 대해 보다 자세히 학습해보고 싶은 사람이 있을 수도 있으므로, 다음 포스트에서는 정규 표현식에 대한 보다 어려운 부분도 다뤄보도록 하겠다.

728x90
반응형
728x90
반응형

이전 포스트에서 정규 표현식이 무엇인지, 메타 문자가 무엇인지에 대해 가볍게 알아보았다. 앞서 학습한 메타 문자를 예제를 기반으로 제대로 다뤄보려면, 자신이 만든 정규 표현식 패턴이 제대로 만들어졌는지를 확인할 필요가 있다.

이번 포스트에서는 Python에서 정규 표현식을 어떻게 지원하는지 re 모듈을 통해 알아보도록 하겠다. 이번 포스트에서 예제로 정규 표현식으로 만든 패턴을 일부 사용하긴 할 것이나, 이는 다음 포스트에서 자세히 다룰 것이므로 이번에는 참고만 하도록 하자.

 

 

 

 

1. Python과 re 모듈

정규 표현식으로 만든 패턴 문자열이 제대로 작동하는지 알기 위해선 Python에서 기본적으로 제공하는 re 모듈에 대해 알 필요가 있다. 

  • re 모듈은 Python에서 정규 표현식을 지원하기 위해 제공하는 기본 라이브러리이다.
  • re 모듈은 컴파일러 객체를 선언하고, 다음과 같은 메서드를 통해 작동한다.
# re 모듈을 가지고 온다.
import re
pattern = "cat"

regex_pattern = re.compile(pattern)
  • re 모듈의 컴파일 객체는 다음과 같은 메서드를 통해 작동한다.
  • 컴파일 객체는 regex_pattern이 아닌 다른 변수명에 담아도 상관없다.
Method/Attribute 기능
match() 문자열의 왼쪽(시작)부터 컴파일된 정규 표현식 패턴 객체가 일치하는지 판단한다.
search() 문자열 전체에서 정규 표현식 패턴 객체와 일치하는 첫 문자의 위치를 찾는다.
findall() 정규 표현식 패턴 객체와 일치하는 모든 문자열을 찾아 리스트로 반환한다.
finditer() 정규 표현식 패턴 객체와 일치하는 모든 문자열을 찾아 이터레이터(Iterator)로 반환한다.

 

 

 

 

1.1. match()

  • match 함수는 왼쪽(시작)부터 정규 표현식 패턴이 일치하는 곳까지를 반환해준다.
  • 왼쪽부터라는 것이 무슨 뜻인지 아리송한 감이 있는데, 이게 정확히 어떻게 돌아가는지 알아보도록 하자.
  • 다음과 같은 정규 표현식 패턴과 Text_data가 있다고 가정해보자.
>>> pattern = "cats"

>>> regex_pattern = re.compile(pattern)

>>> Text_data = "I love cats and I wish I had cats."
  • Text_data에는 pattern인 cats가 들어 있으므로, 문제없이 찾을 수 있을 것 같다.
  • 이를 match 메서드를 이용해서 탐색해보자.
>>> m = regex_pattern.match(Text_data)
>>> print(m)
None
  • 분명히 "cats"이라는 단어가 Text_data 안에 있음에도 탐색되지 않았다.
  • 이는 match() 메서드가 왼쪽에서부터 탐색하기 때문으로, 주어진 "cats"라는 pattern은 Text_data의 문장 중간에 존재하지, 앞에서부터는 존재하지 않기 때문이다.
  • "cats"가 나오도록 패턴을 조금 수정해보자.
    • 새로운 패턴은 다음 포스트에서 자세히 다룰 것이므로, 일단 따라가 보자!
>>> pattern = ".*cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.match(Text_data)
>>> print(m)
<_sre.SRE_Match object; span=(0, 33), match='I love cats and I wish I had cats'>
  • pattern으로 '.*cats'를 사용하였는데, '.*'는 cats 앞에 문자가 0개 이상 존재한다는 의미다.
  • 즉, 0개 이상 문자가 앞에 존재하는 cats와 일치하는 문장을 탐색한 것이다.
  • match 메서드는 _sre.SRE_Match 객체를 리턴한다.
  • _sre.SRE_Match 객체는 Match 된 문자열의 위치, Match 된 문자열을 반환한다.
  • 이를 통해, Match 메서드 시작부터 패턴이 일치하는지 판단한다는 것을 알 수 있다.

 

 

  • 이번에는 pattern을 아주 조금만 수정해서 match() 메서드의 성질을 보다 자세히 보자.
>>> pattern = ".*cats "
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.match(Text_data)
>>> print(m)
<_sre.SRE_Match object; span=(0, 12), match='I love cats '>
  • 앞서 사용한 pattern 뒤에 스페이스(space)를 추가하자, "I love cats "까지만 출력되었다.
  • 즉, match 메서드는 전체 문장이 얼마나 길든 간에 처음부터 pattern과 일치되는 곳까지만 반환하는 것을 알 수 있다.

 

 

1.1.1. match 객체가 제공하는 메서드

  • 앞서 match는 _sre.SRE_Match 객체를 반환한다고 하였는데, 이는 다음과 같은 메서드를 이용해 내부 정보에 접근할 수 있다.
Method / Attribute 기능
group() 매치된 문자열 출력
start() 매치된 문자열의 시작 지점 출력
end() 매치된 문자열의 끝 지점 출력
span() 매치된 문자열의 시작 지점과 끝 지점을 튜플로 출력
  • 예제를 통해 보도록 하자.
>>> pattern = ".*cats "
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.match(Text_data)

>>> print(m.group())
>>> print(m.start())
>>> print(m.end())
>>> print(m.span())
I love cats 
0
12
(0, 12)

 

 

 

 

1.2. search()

  • search 메서드는 문자열 전체에서 정규 표현식 패턴 객체와 일치하는 첫 문자의 위치를 반환한다.
  • 앞서 만든 예제에 그대로 적용해보도록 하자.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.search(Text_data)
>>> print(m)
<_sre.SRE_Match object; span=(7, 11), match='cats'>
  • search 메서드도 match 메서드와 동일하게 _sre.SRE_Match 객체를 반환한다.
  • search 메서드는 match 메서드와 달리 pattern이 처음부터 일치할 필요는 없으며, Text_data 내 가장 먼저 일치하는 문자열의 위치와 문자열을 반환한다.
  • 만약 존재하지 않는 문자열을 검색하면, match와 마찬가지로 None을 반환한다.
>>> pattern = "dogs"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.search(Text_data)
>>> print(m)
None
  • search() 메서드는 match() 메서드와 동일한 _sre.SRE_Match 객체를 반환하므로, match와 동일한 방법을 통해, 원하는 값을 출력할 수 있다.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.search(Text_data)
>>> print(m.group())
>>> print(m.start())
>>> print(m.end())
>>> print(m.span())
cats
7
11
(7, 11)

 

 

 

 

1.3. findall()

  • findall() 메서드는 가장 설명이 깔끔하면서, 유용한 기능을 제공하는 메서드다.
  • 말 그대로, 문장 안에 원하는 텍스트가 존재하면 그 텍스트를 리스트(List)로 모두 가지고 오는 기능으로, 이를 이용해서 해당 문장 안에 패턴에 일치하는 문자열이 몇 개 존재하는지도 쉽게 확인할 수 있다.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.findall(Text_data)
>>> print(m)
['cats', 'cats']
  • match, search와 달리 리스트를 바로 반환하므로, group, start, end, span과 같은 함수를 사용하지 않는다.

 

 

 

 

1.4. finditer()

  • finditer()는 findall()의 진화 버전이라고 할 수 있는 메서드로, Python 초보자라면 그 기능을 알기 쉽지 않다.
  • finditer()와 findall()의 차이는 결과를 이터레이터(Iterator)로 출력할 것인지 리스트(List)로 출력할 것인지로, List로 출력하면, 사용하기는 단순하나 단지 패턴에 일치하는 단어들을 List에 담아 출력하므로, 그 기능이 한정적이다.
  • 그러나 finditer()는 이터레이터를 통해 값을 반환하므로, 더 많은 정보를 담을 수 있고, 엄청나게 큰 Text_data가 대상일 때, 메모리 측면에서도 훨씬 유리하다.
  • 이터레이터에 대해서는 나중에 자세히 다룰 것이므로, 간단하게 내가 원할 때, 순서대로 값을 반환할 수 있는 녀석이라고 생각하면 된다.
  • 이터레이터는 모든 데이터를 메모리에 올려놓지 않기 때문에 findall()보다 용량을 덜 차지한다는 장점이 있다.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.finditer(Text_data)
>>> print(m)
<callable_iterator object at 0x000001EB3D932358>
  • 출력된 결과가 앞서 봤던 match(), search(), findall()과 다르게 전혀 알아볼 수 없는 녀석이 나왔다.
  • Python의 이터레이터는 next() 또는 for와 같은 함수를 이용해야 값을 출력할 수 있다.
# 첫 번째 next
>>> next(m)
<_sre.SRE_Match object; span=(7, 11), match='cats'>

# 두 번째 next
>>> next(m)
<_sre.SRE_Match object; span=(29, 33), match='cats'>

# 세 번째 next
>>> next(m)

  • next()로 이터레이터 안에 있는 값을 꺼내자 _sre.SRE_Match 객체가 반환되는 것을 볼 수 있다.
  • 이터레이터 안에는 2개의 객체만 존재하므로, 마지막 객체 반환 시, StopIteration을 출력하는 것을 볼 수 있다.
  • 이번에는 for문으로 이터레이터 안의 객체를 꺼내보자.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.finditer(Text_data)

>>> for i in m: 
>>>    print(i)
    
<_sre.SRE_Match object; span=(7, 11), match='cats'>
<_sre.SRE_Match object; span=(29, 33), match='cats'>
  • finditer() 메서드는 findall() 메서드와 달리 _sre.SRE_Match 객체를 반환하므로, 문장 내 패턴에 일치하는 대상들의 위치 정보도 쉽게 반환할 수 있다.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.finditer(Text_data)

>>> for i in m:
>>>    print(i.span())

(7, 11)
(29, 33)

 

 

 

 

이번 포스트에서는 Python에서 기본적으로 제공하는 re 모듈에 대해 한 번 다뤄봤다. 텍스트 데이터를 다룰 때, re 모듈은 매우 유용하게 사용되므로 꼭 숙지하도록 하자.

다음 포스트에서는 메타 문자를 이용하여, 예제 데이터에 대한 정규 표현식 패턴을 만들어보고, re 모듈로 이를 검증해보도록 하자.

728x90
반응형
728x90
반응형

이전 포스트까지 문자열(String)을 다루는 기본적인 방법에 대해 알아보았다. 텍스트 데이터의 양이 적고, 패턴이 매우 단순하다면 앞서 다룬 방법만으로도 충분하겠지만, 이를 넘어서는 텍스트 데이터를 다룰 땐 앞서 다룬 방법만으로는 데이터를 다루기가 보다 곤란하다. 이때 등장하는 것이 문자열이 갖는 특정 규칙을 이용해 패턴을 표현하는 정규 표현식이다.

 

 

 

 

1. 정규 표현식(Regular Expression)이란?

위키피디아에선 정규 표현식을 다음과 같이 설명한다 <출처: 위키백과, 정규 표현식>.


정규 표현식은 특정 규칙을 가진 문자열의 집합을 표현하는 데 사용하는 형식 언어로, 정규 표현식은 많은 텍스트 편집기와 프로그래밍 언어에서 문자열의 검색과 치환을 위해 지원하고 있으며, 특히 펄과 Tcl은 언어 자체에 강력한 정규 표현식을 구현하고 있다.


이를 이해하기 쉽게 아~~~주 간단하게 표현하면 다음과 같다.

"대상 문자열이 가지고 있는 특정 패턴을 정규 표현식이라는 이름의 패턴 문자열을 이용해서 찾아내는 방법"

당신이 "연락처"라는 데이터를 수집했다고 가정해보자, 응답자들은 단순히 연락처를 적으라고 하였으므로, 휴대전화 번호, 집전화번호, 이메일 주소와 같은 다양한 종류의 연락처를 입력하였고, 당신의 보스는 당신에게 휴대전화 번호와 집전화번호, 이메일 주소를 각각 분리해서 가지고 오라고 지시를 하였다. 애초에 이를 따로 수집했으면 이런 귀찮은 일도 안 생기겠지만, 이미 저질러진 일을 어쩌겠는가!라는 상황에서 유용하게 사용할 수 있는 것이 정규 표현식이다.

정규표현식에 대한 이해를 돕기 위해 수집한 데이터의 예시를 눈으로 보도록 하자.

data = [
    "010.1234.5678", "010-1234-5678", "02-1234-5678", "031-123-4567", "042.987.6543",
    "emailID01@naver.com", "emailID02@gmail.com", "Email.03@daum.net", "EM_example.01@gmail.com",
    "email_1234@company.co.kr"
]

위 예제 데이터를 보면, 휴대전화 번호와 집전화번호, 이메일 주소마다 고유한 패턴을 가지고 있는 것을 볼 수 있다.

  • 다음 패턴은 위 데이터만 대상으로 놓고 분류한 것이다.
  • 휴대전화 번호는 010 특수 기호 '.', '-' 숫자 4자리 '.', '-' 숫자 4자리로 구성되어 있다.
  • 집전화번호는 0 뒤에 1~9의 숫자가 1개에서 2개가 오고 그 뒤에 특수 기호 '.', '-' 숫자 3 ~ 4 자리, '.', '-' 숫자 4자리로 구성되어 있다.
  • 이메일 주소는 ID와 Domain으로 나눠지며, 그 사이에는 '@'가 존재한다.
  • ID는 영문 대, 소문자와 숫자, 특수문자 '.', '_"로 이루어져 있으며, 맨 앞에는 특수문자가 들어갈 수 없다.
  • Domain은 소문자로만 이루어져 있으며, 특수문자 '.'를 기준으로 소문자가 1개에서 2개의 그룹으로 연결된다.

정규 표현식은 위 패턴을 하나의 문법으로 만들어 그 문법에 해당하는 대상을 찾거나 바꿀 수 있는 도구다. 즉, 휴대전화 번호만 찾고 싶다면 휴대전화 번호의 기본적인 패턴을 문법으로 만들고 그 문법에 해당하는 텍스트 데이터를 조회하여, 휴대전화 번호만 가지고 오게 할 수 있다.

 

※ 팁

한 가지 팁이라면, 데이터만 보고 정규 표현식을 사용하는 것보다. 해당 데이터의 생성 방식을 먼저 찾아보고 정규 표현식을 사용하는 것이 일반화 측면에서 더 좋다. 다음과 같은 정보는 잠깐만 인터넷 검색을 해보면 알 수 있는 정보이다.

  • 휴대전화 번호의 통신망 식별번호
    • 010, 011, 016, 017, 018, 019
  • 지역번호
    • 02, 031, 032, 033, 041, 042, 043, 044, 051, 052, 053, 054, 055, 061, 062, 063, 064

 

정규 표현식 문법은 각종 특수문자가 섞여 있고, 문법 작성 방식이 가독성과 거리가 꽤 멀기 때문에 지저분해 보인다는 단점이 있어, 많은 데이터 분석 입문자들이 기피하는 경향이 있다. 그러나, 텍스트 데이터를 다룰 때, 정규 표현식을 다룰 수 있고 없고의 차이는 매우 크므로, 조금 고통스럽더라도 함께 공부해보도록 하자(막상 공부해보면 보기보다 할만하다!).

 

 

 

 

2. 정규 표현식 문법과 메타 문자

  • Python으로 정규 표현식을 다루는 법을 알기 전에 먼저 정규 표현식 문법부터 알아보도록 하자.
  • 정규 표현식은 언어마다 문법이 조금씩 다르므로, 사용하려는 프로그램 언어에 맞는 정규 표현식 문법을 공부하는 것이 좋다.
  • 정규 표현식 사용 방법은 일반적으로 다음과 같은 순으로 진행된다.
  1. 정규 표현식 문법 작성
  2. 작성된 문법을 이용한 조작(탐색, 대체 등)

 

 

2.1. 메타 문자(Meta characters)

  • 평상시, 큰 의미 없이 사용했던 특수 문자들이 정규 표현식에서는 특정한 기능을 갖는데, 이를 메타 문자라고 한다.
  • 정규 표현식 문법은 이 메타 문자와 찾고자 하는 Text 데이터로 구성된다.
  • 이 특수 문자를 문자 그대로 사용하려면 앞에 '\'(역 슬래시)를 넣어줘야 한다.
  • 역 슬래시는 한국인이 사용하는 일반적인 키보드에서 ''로 표기되며, ''는 보이기에만 이렇지 실제로는 역 슬래시로 작용하므로, 이를 그냥 사용해도 문제없다.
  • 메타 문자는 크게 '문자의 패턴', '문자의 반복', '문자열의 위치', '기타 기능'으로 구성된다.
  • 대표적인 메타 문자는 다음과 같다.
문자의 패턴 문자의 반복
[] 문자 클래스 + 앞 문자 패턴이 1개 이상
. 임의의 한 문자 * 앞 문자 패턴이 0개 이상
\d 숫자 ? 앞 문자가 없거나 하나 있음
\D 숫자가 아닌 것 {n, m} 앞 문자 패턴이 n개 이상 m개 이하
\w 문자, 숫자, _ {n, } 앞 문자 패턴이 n개 이상
\W 문자, 숫자, _가 아닌 것 {n} 앞 문자 패턴이 n개
\s 공백 문자    
\S 공백 문자가 아닌 것    

 

 

문자열의 위치 기타 기능
^ 문자열의 시작 | 또는(or)
$ 문자열의 끝 () 문자열 패턴의 그룹화

 

 

이번 포스트에서는 정규 표현식이 무엇인지 알아보고, 정규 표현식에 사용되는 메타 문자가 무엇인지 알아보았다. 다음 포스트에서는 Python에서 정규 표현식의 기능을 어떻게 제공하는지 파악해보도록 하자.

728x90
반응형
728x90
반응형

지금까지 Index와 위치를 이용해서 데이터를 조회하는 방법에 대해 알아보았다. Index, 위치를 이용하여 행을 조회하는 것이 가장 빠르지만, 현실적으로 내가 원하는 데이터의 Index와 행의 위치를 미리 아는 것은 불가능하므로 이번 포스팅에서는 특정 조건으로 내가 원하는 행을 찾아내는 방법에 대해 학습해보도록 하겠다.

 

 

 

Pandas와 Boolean

불리언(Boolean)은 논리 자료형이라고도 하며, 참(True)과 거짓(False)을 나타낼 때 사용된다. Python에서 참(True)은 1, 거짓(False)은 0의 정수 값을 가지며, 이 특성을 잘 활용하면 재밌는 코드를 많이 만들어낼 수 있다.

>>> print(f"True의 Type: {type(True)}")
>>> print(f"False의 Type: {type(False)}")
>>> print("----"*10)
>>> print(f"True를 수치로 표현 시: {int(True)}")
>>> print(f"False를 수치로 표현 시: {int(False)}")
>>> print("----"*10)
>>> print(f"True를 수치로 표현 시: {float(True)}")
>>> print(f"False를 수치로 표현 시: {float(False)}")
True의 Type: <class 'bool'>
False의 Type: <class 'bool'>
----------------------------------------
True를 수치로 표현 시: 1
False를 수치로 표현 시: 0
----------------------------------------
True를 수치로 표현 시: 1.0
False를 수치로 표현 시: 0.0

Pandas는 Boolean Series를 이용하여 True인 행은 출력하고, False인 행은 출력하지 않을 수 있다. 이를 더 쉽게 이야기해보면, "내가 원하는 행만 True로 만들면 그 행만 조회할 수 있다"는 것이다.

 

 

 

 

1. 예제 데이터와 라이브러리 가지고 오기

※ 예제 데이터

이번 포스트에서도 지금까지 사용한 "시험점수.csv" 파일을 예제 데이터로 사용해보도록 하겠다.

사용할 라이브러리는 pandas지만, pandas와 함께 다니는 친구인 numpy도 혹시 쓸 일이 있을지 모르니 가져와주도록 하자.

시험점수.csv
0.00MB

 

>>> import pandas as pd
>>> import numpy as np

>>> FILE_PATH = "시험점수.csv"
>>> 시험점수 = pd.read_csv(FILE_PATH, encoding="euckr")
>>> 시험점수

  • 해당 예제 데이터는 이름, 성별, 학년, 반, 국어, 수학, 영어, 사회, 과학 총 9개의 열과 80개의 행으로 구성되어 있다.
  • 한글이 깨지는 경우, 위 코드처럼 pd.read_csv(File_path, encoding='euckr')로 불러오면 된다.

 

 

 

 

2. Series와 Boolean

  • DataFrame에서 조회할 대상을 가정해보자.

  • 조회할 대상: 3학년이고 2반에 있으며, 국어 점수가 70점 이상인 여학생을 찾는다.

  • 위 대상에 해당하는 속성이 4개나 되므로, 한 번에 찾기는 쉽지 않다.
  • 위 문구를 구성하는 조건을 최소 단위로 쪼개보자.

  • 조건 1: 학년 == 3
  • 조건 2: 반 == 2
  • 조건 3: 국어 >= 70
  • 조건 4: 성별 == "여자"

  • 문구를 최소 단위로 쪼개 보니 꽤 단순해졌다!
  • 방금 정의한 "조회할 대상"은 위 조건 4개에 대해 모두 참(True)인 행이다.
  • 즉, 위 조건 4개에 대해 모두 True인 행을 표현만 할 수 있다면, 위 행에 해당하는 사람의 데이터만 가지고 올 수 있다는 것이다.
  • 지금까지 우리는 Pandas에서 다양한 변수를 담고 있는 데이터(다변량 데이터) 타입인 DataFrame을 다뤄봤는데, 이번엔 단 하나의 변수만 담고 있는 데이터(단변량 데이터) 타입인 시리즈(Series)로 접근을 해보도록 하자.

 

 

 

2.1. 단변량 데이터를 담는 시리즈(Series)

  • 시리즈는 데이터 프레임에서 하나의 컬럼만 뽑으면 쉽게 만들 수 있다.
  • 데이터 프레임 "시험점수"에서 "성별" 시리즈를 뽑아보자.
>>> 시험점수["성별"]
0     남자
1     남자
2     남자
3     남자
4     남자
      ..
75    여자
76    여자
77    여자
78    여자
79    여자
Name: 성별, Length: 80, dtype: object
  • 시리즈는 각 Value에 대한 Index 정보, 시리즈의 이름(Name), 시리즈를 구성하는 원소의 수(Length), 시리즈의 데이터 타입(dtype) 정보를 담고 있다.
  • 이를 하나의 변수만 담고 있는 데이터 프레임이라고 생각해도 큰 문제는 없다.
    • 시리즈는 데이터 프레임에서 사용 가능한 함수의 대부분을 지원하므로, 데이터 프레임을 쓸 줄 안다면 큰 어려움 없이 익숙해질 수 있다.
    • 단, "시리즈 = 하나의 변수만 담긴 데이터 프레임"은 절대 아니다! 주의하도록 하자!
  • 시리즈는 DataFrame을 만들었던 방법처럼 list나 numpy array 등을 이용해 쉽게 만들 수 있다.
>>> Test_list = ["감", "바나나", "포도", "딸기"]
>>> pd.Series(data=Test_list, name="과일")
0      감
1    바나나
2     포도
3     딸기
Name: 과일, dtype: object

 

 

 

 

3. Boolean Series를 이용하여 원하는 행을 조회하기.

3.1. 단변량 데이터인 시리즈에 조건 걸기

  • "시험점수" 데이터 프레임에서 "성별" 시리즈를 뽑고, "여자"인 대상을 찾아보자.
  • Python은 기본적으로 다음과 같은 비교 연산자를 제공한다.
같다 다르다 크다 작다 이상 이하
기호 == != > < >= <=
  • 위 비교 연산자를 이용하여 "성별" == "여자"인 대상자를 찾아보자.
>>> 시험점수["성별"] == "여자"
0     False
1     False
2     False
3     False
4     False
      ...  
75     True
76     True
77     True
78     True
79     True
Name: 성별, Length: 80, dtype: bool
  • 비교 연산자를 사용하자 True와 False로 이루어진 Series가 출력되었다.
  • 이 Series에서 True는 "성별" == "여자"인 행을 뜻하며, False는 "성별" != "여자"인 행을 뜻한다.
  • 위에서 생성한 Boolean 시리즈를 이용해서, 제대로 탐색을 하였는지 확인해보도록 하자.
>>> 시험점수[시험점수["성별"] == "여자"].head(10)

  • 출력되는 결과가 매우 길어 df.head(n) 함수를 이용해 상위 n개의 행만 출력되게 하였다.
  • Pandas의 데이터 프레임은 df[Boolean Series]를 하면, Boolean Series에서 True에 해당하는 행만 출력한다.
  • 앞서, Boolean은 True는 1, False는 0의 값을 갖는다고 하였는데, 이 성질을 이용하면 "성별"=="여자"인 대상의 수를 알 수 있다.
>>> sum(시험점수["성별"] == "여자")
40
  • sum()은 괄호 안의 list, array, series 등의 Data type에 대해 합계를 내주는 함수로, 40이 나왔으므로 1이 40개 있다는 것을 보여준다.
  • 이는 "성별"=="여자"인 행이 40개 있다는 것으로, 여성의 수가 40명이라는 것을 의미한다.
  • 등호가 돌아가는 원리를 알았으니 이번엔 부등호를 조건으로 걸어보자.

 

  • 이번엔 부등호로 조회를 해보자.
  • 이번에는 국어 점수가 70점 이상인 대상을 찾아보자.
>>> 시험점수[시험점수["국어"] >= 70].head(10)

  • 국어 점수가 70점 이상인 상위 10명을 뽑아 보았다.
  • 부등호도 등호와 마찬가지로 쉽게 뽑을 수 있다.

 

 

 

 

3.2. 동시에 2개 이상의 조건 걸기

  • 앞서 시리즈를 이용해 단변량 데이터에 대해 조건을 걸어보았다.
  • 이번엔 2개의 시리즈를 동시에 사용하여 다변량 데이터에 대한 조건을 걸어보도록 하겠다.
  • 이때 사용되는 것이 옛날에 배웠던 집합(Set)이다.
  • Python에서 주로 사용되는 집합은 교집합, 합집합, 차집합이며, 이들은 다음과 같은 기호를 통해 사용할 수 있다.
집합 교집합 합집합 차집합
기호 & | -
  • 2개의 Boolean Series를 가지고 와보자.
>>> 여자_Boolean_Sr = 시험점수["성별"] >= "여자"
>>> 국어_Boolean_Sr = 시험점수["국어"] >= 70

>>> print(여자_Boolean_Sr)
>>> print("----"*10)
>>> print(국어_Boolean_Sr)
0     False
1     False
2     False
3     False
4     False
      ...  
75     True
76     True
77     True
78     True
79     True
Name: 성별, Length: 80, dtype: bool
----------------------------------------
0      True
1     False
2      True
3      True
4      True
      ...  
75    False
76     True
77    False
78    False
79     True
Name: 국어, Length: 80, dtype: bool
  • 두 Boolean 시리즈를 교집합 해보자.
>>> 여자_Boolean_Sr & 국어_Boolean_Sr
0     False
1     False
2     False
3     False
4     False
      ...  
75    False
76     True
77    False
78    False
79     True
Length: 80, dtype: bool
  • 두 시리즈 모두 True인 행만 True로 나오고, 둘 중 하나라도 False인 행은 False가 되었다.
  • 교집합은 두 가지에 모두 해당해야 한다는 의미이므로, 모두 True인 대상을 가지고 온다.
  • 즉, 두 조건에 동시에 해당하는 행만 교집합 시, True로 출력된다.
>>> 시험점수[여자_Boolean_Sr & 국어_Boolean_Sr].head(10)

  • 이를 이용하면, 조건이 아무리 많다고 할지라도 내가 원하는 대상을 구체적으로 찾아낼 수 있다.
  • 남은 조건인 "학년"==3, "반"==1을 추가하여 앞서 선언한 "조회할 대상"을 찾아보자.
>>> 시험점수[
        (시험점수["성별"] == "여자") &
        (시험점수["학년"] == 3) &
        (시험점수["반"] == 1) &
        (시험점수["국어"] >= 70)
        ]

  • 꽤 복잡한 조건이었음에도 Boolean Series를 사용하여 쉽게 찾아내었다.
  • 2개 이상의 Boolean Series를 변수에 담지 않고 바로 조회하려면 꼭 괄호 안에 넣어주기 바란다(괄호 미사용 시 에러 발생).

 

 

 

 

지금까지 조건에 해당하는 행 조회 방법에 대해 알아보았다. 다음 포스트에서는 Pandas의 str을 이용하여, 원하는 문자열에 해당하는 행을 탐색하는 방법에 대해 다뤄보도록 하겠다.

728x90
반응형
728x90
반응형

이전 포스트에서는 DataFrame의 Index를 기반으로 데이터를 조회하는 방법에 대해 알아보았다. 이번 포스트에서는 위치를 기반으로 데이터를 조회하는 슬라이싱(Slicing)과 iloc[]함수에 대해 알아보도록 하겠다.

 

 

 

 

슬라이싱(Slicing)을 이용한 데이터 조회

이전 포스트에서 데이터를 조회하는 방법으로 다음과 같은 방법이 있다고 소개하였다.

  1. 행의 고유한 key인 Index를 이용하여 내가 원하는 데이터 조회
  2. 데이터의 위치를 이용해서 데이터 조회(Slicing)
  3. 조건을 이용한 데이터 조회(Boolean)

이번 포스트에서는 "2. 데이터의 위치를 이용한 데이터 조회(Slicing)"에 대해 다뤄보도록 하겠다.

경우에 따라선 데이터의 위치가 조회에 있어 중요한 key로 작동하기도 한다. 그 예는 다음과 같다.

  • 고등학교 중간고사 전교 30등까지의 데이터를 조회
  • 30명이 있는 반에서 6 ~ 12번째 주번이 누구인지 조회
  • 월 소득의 상위 10% 데이터 조회 또는 하위 10% 조회

위와 같은 위치를 이용한 조회를 하려면 기본적으로 행의 순서가 일정한 규칙에 따라 나열되어 있어야 한다. 데이터가 아무런 규칙 없이 위치해 있다면 사용하기 힘들다. 때문에 규칙대로 데이터를 나열하는 정렬(Sort)과 함께 쓰이는 경우가 잦다.

Pandas의 DataFrame은 기본적으로 0부터 n까지의 index를 순서대로 자동 생성하므로, Index를 기반으로 한 조회와 위치를 이용한 조회의 차이를 알기 어려울 수 있으나 Index와 위치는 다음과 같은 차이를 갖는다.

  1. Index는 단순 행의 위치뿐만 아니라 다양한 정보를 담은 특정한 key로 만들 수 있다.
  2. 위치 기반 조회는 Index로부터 자유롭게 단순 위치로 데이터를 조회할 수 있다.

예제를 통해 위치를 이용한 데이터 조회를 해보도록 하자.

 

 

 

 

1. 예제 데이터 가지고 오기

※ 예제 데이터

이번 포스트에서도 저번 포스트에서 사용한 예제 데이터를 그대로 사용해보도록 하겠다.

시험점수.csv
0.00MB

>>> import pandas as pd
>>> import numpy as np

>>> FILE_PATH = "시험점수.csv"
>>> 시험점수 = pd.read_csv(FILE_PATH, encoding="euckr")
>>> 시험점수

  • 해당 예제 데이터는 이름, 성별, 학년, 반, 국어, 수학, 영어, 사회, 과학 총 9개의 열과 80개의 행으로 구성되어 있다.
  • 한글이 깨지는 경우, 위 코드처럼 pd.read_csv(File_path, encoding='euckr')로 불러오면 된다.

 

 

 

 

2. 순서를 이용해서 데이터를 가지고 오기

  • 순서를 이용해서 데이터를 가지고 오는 방법은 크게 2가지로 다음과 같은 코드를 사용할 수 있다.
  1. df[start:end]
  2. df.iloc[]
  • 두 함수는 기능은 거의 동일하나, 엄밀히 따지면 첫 번째 코드인 df[start:end]를 슬라이싱(Slicing)이라 하며, df.iloc[]는 어려운 말로 명시적 포지션 검색이라 한다.
  • 간단하게 말해서 첫 번째 녀석은 start부터 end까지를 잘라서 보여주는 것이고, 두 번째 녀석은 해당 위치에 있는 것을 가져오는 것으로 이전 포스팅에서 소개한 df.loc[]와 성격이 유사하다.

 

 

2.1. 슬라이싱을 통한 데이터 조회

  • 예제 데이터인 "시험점수"의 10번부터 20번까지 데이터를 조회해보도록 하자.
  • df[start:end]
시험점수[10:20]

  • 아주 간단한 코드를 통해 데이터를 자를 수 있다.
  • 슬라이싱을 통해 가지고 오는 행의 번호는 start 이상, end 미만이다.
  • 슬라이싱은 단순히 처음과 끝만 설정할 수 있는 것이 아니라 간격도 설정해줄 수 있다.
  • DataFrame 시험점수의 10번부터 20번까지를 2를 간격으로 조회해보도록 하자.
  • df[start:end:step]
>>> 시험점수[10:20:2]

  • start나 end 둘 중 하나를 지정해주지 않으면, "start부터 끝까지" 또는 "처음부터 end"까지로 슬라이싱할 수도 있다.
  • 68번째 이후의 데이터를 전부 조회해보고, 처음부터 15번째까지의 데이터를 조회해보도록 하자.
>>> 시험점수[68:]

>>> 시험점수[:15]

  • 여기서도 간격을 설정해줄 수 있다.
>>> 시험점수[:15:2]

 

 

 

 

2.2. 슬라이싱을 이용한 역순 조회

2.2.1. Step을 이용한 역순 조회

  • 슬라이싱의 각 인자들을 음수로 부여하면 역순으로 조회할 수 있다.
  • 전체 데이터를 역순으로 조회하려면 step을 -1로 지정하면 된다.
>>> 시험점수[::-1]

  • step을 -1로 설정하고 start를 지정해주면 start부터 역순 조회도 가능하다.
>>> 시험점수[4::-1]

  • step을 -1로 설정하고 end를 설정하면, 맨 뒤에서부터 시작하여 end까지로 조회된다.
>>> 시험점수[:4:-1]

  • step을 음수로 지정해주면, DataFrame의 순서를 깔끔히 뒤집어 조회하는 것을 볼 수 있다.

 

2.2.2. Step을 지정하지 않은 역순 조회

  • step을 음수로 지정하고 조회하면 데이터가 반대로 뒤집혀서 조회되는 것을 보았다.
  • step을 음수로 지정하지 않고 start나 end만 음수로 지정하면 데이터를 뒤집지 않은 본래의 상태에서 뒤에서부터 조회가 된다.
  • start를 음수 m으로 지정하고 end를 지정하지 않으면 뒤에서부터 m개의 행을 반환한다.
>>> 시험점수[-4:]

  • start를 지정하지 않고 end를 음수 n으로 지정하면 뒤에서부터 n개의 행을 제외하고 반환한다.
>>> 시험점수[:-4]

  • start를 음수 m으로 end를 음수 n으로 입력 시 뒤에서부터 m:n의 행을 가지고 온다.
  • 단, start의 음수 m이 end의 음수 n보다 커야 한다(그렇지 않다면, 행이 하나도 조회되지 않는다).
>>> 시험점수[-7:-4]

>>> 시험점수[-4:-4]

>>> 시험점수[-2:-4]

 

 

 

 

2.3. 슬라이싱과 인덱스

  • 슬라이싱은 기본적으로 행의 순서를 기반으로 행을 조회하지만, 인덱스를 대상으로도 조회가 가능하긴 하다.
  • 시험점수 DataFrame의 "이름"칼럼을 Index로 설정하고, "지원"부터 "민서"까지의 행을 조회해보자.
>>> 시험점수.set_index("이름", inplace=True)
>>> 시험점수["지원":"민서"]

  • 해당 기능은 앞서 다뤘던 Index의 범위를 이용한 조회와 유사하다.
>>> 시험점수.loc["지원":"민서"]

  • 다만, 해당 방법은 중복이 있는 Index에 대해서 Slicing을 할 곳을 특정할 수 없으므로, 동작하지 않는다.
>>> 시험점수.loc["영호":"하윤"]

  • 또, 인덱스를 다른 숫자로 정의한다면, 인덱스를 무시하고 순서로 슬라이싱을 한다.
# 30에서 110까지의 정수를 새로운 인덱스로 정의하였다.
>>> 시험점수.reset_index(drop=False, inplace=True)
>>> 시험점수.index = range(30, 110)
>>> 시험점수

>>> 시험점수[30:40]

  • 30에서 110으로 Index를 바꿨으므로, Index로 슬라이싱 되면 30에서 40까지의 Index로 출력되어야 하나, 30번째 행에서 40번째 행에 위치한 Index인 60~69가 출력되었다.

 

 

 

 

2.4. df.iloc[]를 이용한 조회

  • 앞서 슬라이싱을 이용한 순서 기반 조회를 해보았다.
  • 슬라이싱은 Pandas의 DataFrame, Series뿐만 아니라 list, numpy array 등 Python의 다양한 data type에서 지원하는 기능이지만, Pandas는 여기에 더해서 df.iloc[] 또는 Sr.iloc[] 함수를 통해 위치 기반 조회를 할 수 있다.
>>> 시험점수.reset_index(drop=True, inplace=True)
>>> 시험점수.loc[5]
이름     순자
성별     여자
학년      1
반       1
국어     80
수학    100
영어     75
사회     70
과학     65
Name: 5, dtype: object
>>> 시험점수.loc[5:10]

  • iloc는 기본적으로 슬라이싱과 기능이 매우 유사하므로, 슬라이싱에서 다뤘던 기능들이 대부분 동일하게 실행된다.
  • 단, iloc는 인덱스 기반 조회는 불가능하다.
>>> 시험점수.set_index("이름", inplace = True)
>>> 시험점수.iloc["지원":"민서"]

 

 

 

이번 포스트에서는 위치를 기반으로 필요한 행을 조회하는 슬라이싱과 iloc함수를 알아보았다. 다음 포스트에서는 특정 조건을 기반으로 행을 조회하는 Boolean 기반 행 조회 방법에 대해 알아보도록 하자.

728x90
반응형
728x90
반응형

얼마만의 포스팅인지... 가장 최근에 한 포스팅이 2021년 05월 17일이니, 반년 조금 넘는 기간 동안 포스팅을 안 했다.

나름의 변명이라면 올해 4월에 새로운 직장에 가고, 프로젝트를 하나 둘 맡아서 하다 보니 정신이 없기도 했고, 프로젝트를 어느 정도 마무리 짓고 여유가 나자 그놈의 디아블로 2가 나와버리는 바람에 시간이 슉 지나가버렸다.

너무도 감사하고, 죄송스럽게도 많은 분들께서 찾아와 주셔서, 미약한 실력이나마 도움이 될 수 있기를 바라며 다시 한번 포스팅을 해보고자 한다. 과연, 포스팅하고자 했던 것들을 전부 올리고, 새로운 영역을 파헤쳐 갈 수 있을까?

 

 

 

 

데이터 프레임, 데이터 조회하기

Python을 사용하는 데이터 분석가에게 가장 기초가 되는 Pandas를 다룬 것이 올해 초인 2021.02.24일이었으니, 다음에 뭘 다루려 했었는지 가물가물하다. 앞서 DataFrame의 Index와 Column명을 가지고 노는 법에 대해 알아봤으니, 이번 포스팅에서는 Index와 Column명을 이용해서 내가 원하는 데이터만 가지고 오는 법에 대해 알아보도록 하겠다.

데이터 프레임의 가장 큰 장점은 누구든지 이해하기 쉬운 형태로 데이터를 볼 수 있다는 것이지만, 데이터의 양이 많아질수록 한눈에 들어오지 않을 정도로 데이터가 거대해지기 때문에, 내게 필요한 데이터만 조회하는 법을 알아야 한다.

데이터 프레임의 조회 방식은 크게 index와 column 명으로 조회하는 방법, 데이터의 순서를 기반으로 조회하는 방법,  Boolean으로 조회하는 방법이 있는데, 이를 찬찬히 알아보도록 하자.

 

내가 원하는 데이터를 가지고 오기

  • 내가 원하는 데이터를 가지고 오는 방법은 크게 3 가지가 있다.
  1. 내가 원하는 행을 가지고 오기
  2. 내가 원하는 열을 가지고 오기
  3. 내가 원하는 행과 열을 가지고 오기
  • 여기서 내가 원하는 행을 가지고 오는 방법이 비교적 복잡하며, 원하는 행을 가지고 온 후 내가 필요한 열을 설정해주면, 문제는 쉽게 해결된다.
  • 그러므로, 행을 가지고 오는 방법을 위주로 다뤄보도록 하자.
  • Pandas의 DataFrame에서 내가 원하는 행을 가지고 오는 방법도 크게 3가지가 있다.
  1. Index를 이용해서 가지고 오기(loc)
  2. 위치를 이용해서 가지고 오기(슬라이싱, iloc)
  3. 조건을 이용해서 가지고 오기(Boolean)
  • 이번 포스트에서는 Index를 이용해 내가 원하는 데이터를 가지고 오는 방법에 대해 다뤄보도록 하자.

 

 

 

 

1. 예제 데이터 가지고 오기

※ 예제 데이터

해당 예제 데이터는 이름, 성별, 학년, 반, 국어, 수학, 영어, 사회, 과학 총 9개의 열(Column)과 80개의 행(Row)으로 구성된 csv파일이다. 당분간 이 데이터로 학습을 진행해보도록 하자.

시험점수.csv
0.00MB

  • 다운로드 받은 위 예제 데이터("시험점수.csv")파일을 내 워킹 디렉토리(Working Directory)로 이동시키거나, 파일의 경로를 이용해서 불러와보도록 하자.
>>> import pandas as pd
>>> import numpy as np

>>> FILE_PATH = "시험점수.csv"
>>> 시험점수 = pd.read_csv(FILE_PATH, encoding="euckr")
>>> 시험점수

  • R과 비교되는 파이썬의 가장 큰 장점은 한글 사용이 비교적 자유롭다는 것이다.
  • 파이썬은 utf-8 기반으로 만들어져 있기 때문에 데이터뿐만 아니라 변수명을 한글로 써도 큰 문제가 없다.
  • 혹시 모르는 에러가 발생할 위험이 있으므로, 그래도 영어를 사용하는 것을 추천한다!!
  • 한글이 들어 있는 파일이라면 파일의 경로만을 이용해서 데이터를 가져오는 경우, 한글이 깨지는 경우가 있는데, 이는 대부분 Encoding 문제로, pd.read_csv() 함수의 encoding 파라미터를 "euckr"로 지정하면 거의 해결된다.

 

 

 

 

2. Index를 이용해서 데이터를 가지고 오기.

  • 데이터 프레임에서 내가 원하는 행을 조회하는 가장 빠른 방법은 Index를 이용하는 것이다.
    • Index는 유일하게 존재하는 고유한 값일 수도 있으나, 중복된 값일 수도 있다.
    • Index를 이용한다면, 데이터 조회뿐만 아니라 데이터 병합(Merging)도 쉽게 할 수 있다.
  • Index로 조회하는 방법은 다음과 같다.
  • df.loc[index]: index에 해당하는 DataFrame의 행을 가지고 온다.

 

 

2.1. 하나의 index를 가지고 와보자.

  • index 60번을 가지고 와보자.
>>> 시험점수.loc[60]
이름    민준
성별    남자
학년     4
반      1
국어    90
수학    75
영어    80
사회    70
과학    60
Name: 60, dtype: object

>>> type(시험점수.loc[60])
pandas.core.series.Series
  • 하나의 index를 조회하자, Series Type으로 해당 index 정보가 조회되는 것을 볼 수 있다.
  • 이 시리즈에서 이름을 하나의 값으로 가지고 와보도록 하자.
>>> Index60_Sr = 시험점수.loc[60]
>>> Index60_Sr["이름"]
'민준'
  • 파이썬의 딕셔너리(Dictionary) 객체처럼 내가 원하는 변수명을 key로 주니, 내가 원하는 값이 반환되는 것을 볼 수 있다.

 

 

 

 

2.2. 여러 개의 index를 가지고 와보자.

  • 이번에는 여러 개의 index를 한 번에 가지고 와보도록 하자.
  • 15, 20, 30, 45번의 Row를 가지고 와보도록 하겠다.
  • 여러개의 index를 한번에 조회는 내가 원하는 index들이 들어있는 list를 만들어 loc에 넣으면 된다.
>>> 시험점수.loc[[15,20,30,45]]

  • 이전과 다르게 이번에는 index에 해당하는 데이터 프레임으로 결과가 조회되는 것을 볼 수 있다.
  • 위 방법을 응용하면 Index A부터 Index B까지 조회도 가능하다.
>>> index_range = range(15,20) 
>>> 시험점수.loc[index_range]

>>> 시험점수.loc[15:20]

  • 다만 Index의 범위를 이용하여 탐색하는 방법은 위 방법보다 다음에 다룰 Slicing이 더 많이 사용되므로, 이런 것도 있구나~ 하고 넘어가도록 하자.

※ 주의 사항

  • 일반적인 파이썬 슬라이싱 방식은 start:end 일 경우, start부터 end-1까지의 연속된 값을 반환하나, .loc[start:end]는 start 부터 end 까지의 연속된 index를 조회한다.
시험점수[15:20]

  • 일반적인 슬라이싱 방식은 위 방식으로 하며, DataFrame.loc[Index]는 Index에 속해 있는 모든 index의 행을 가지고 온다.

 

 

 

 

2.3. index와 column으로 조회해보자.

  • 해당 Index에서 특정 Column의 데이터만 보고 싶다면, index와 column을 동시에 넣어 조회하면 된다.
  • 앞서 소개한 df.loc[index]를 아래와 같이 쓰면 된다.
    • df.loc[index, column]
  • 16번 index의 국어 점수를 조회해보자.
>>> 시험점수.loc[16, "국어"]
75
  • 해당 방법을 통해 내가 필요한 값을 하나의 원소로 출력하였다.
  • 이번에는 index 16의 이름, 학년, 반, 국어 점수를 동시에 출력해보도록 하자.
>>> 시험점수.loc[16, ["이름", "학년", "반", "국어"]]
이름    정숙
학년     1
반      2
국어    75
Name: 16, dtype: object
  • 내가 필요한 index들을 list에 담아 조회하였듯, 조회하고자 하는 column들을 list에 넣어 조회하면 해결된다.
  • 이번엔 index를 16, 20, 25로 column을 이름, 학년, 반, 국어로 조회해보자.
>>> 시험점수.loc[[16, 20, 25], ["이름", "학년", "반", "국어"]]

 

 

 

 

2.4. 값을 Index에 넣어 조회해보자.

  • 앞서 다뤘던 방법들은 내가 원하는 Index를 알고 있다는 전제하에 유용하지만, 대부분 Index는 자동으로 설정되는 0~n 사이의 값이므로 이를 활용하는 것은 쉽지 않다.
  • 이럴 땐, 특정 열을 Index로 만들어 조회하는 것으로 이 문제를 해결할 수 있다.
  • "이름" column을 Index로 만들어보자.
  • df.set_index(column, drop=True, inplace=False): column을 df의 index로 설정한다.
>>> 시험점수.set_index("이름", inplace=True)
>>> 시험점수

  • inplace = True로 설정하면, df.set_index의 대상인 df에 바로 이를 적용할 수 있다.
  • default인 inplace를 False로 하면 df.set_index의 결과만 출력한다.
  • drop=False로 설정하면, 기존 index를 제거하지 않고 데이터 프레임의 열로 추가한다.
  • index를 "영철"로 조회해보자.
>>> 시험점수.loc["영철"]

  • DataFrame의 Index는 고유한 값이 아니어도 설정 가능하므로, 위와 같이 "이름"이 "영철"에 해당하는 Row를 모두 가지고 오는 것을 볼 수 있다.
  • Index를 초기 상태로 돌려보자.
>>> 시험점수.reset_index(drop=False, inplace=True)
>>> 시험점수

  • 이번에는 학년이 1학년인 사람들만 조회해보도록 하자.
>>> 시험점수.set_index("학년", inplace=True)
>>> 시험점수.loc[1]

 

 

 

 

2.5. 다중 값을 Index로 조회해보자.

  • 지금까지 하나의 열만 Index로 넣어 조회를 해보았다.
  • 그렇다면, 여러 개의 열을 Index에 넣어 내가 원하는 행만 조회할 수는 없을까?
  • 이때는 크게 2가지 방법이 있다.
  1. Multi Index를 만들어 조회하기
  2. 조회를 위한 열을 만들기
  • 먼저 Multi index를 만드는 법부터 알아보도록 하자.
  • Index를 다시 초기화하고 학년과 반으로 Multi index를 만들어보도록 하겠다.
>>> 시험점수.reset_index(drop=False, inplace=True)
>>> 시험점수.set_index(["학년", "반"], inplace=True)
>>> 시험점수

  • 2학년 1반 사람들을 조회해보도록 하자.
  • Multi Index는 Python의 Tuple로 생성되므로, Tuple로 조회하면 된다.
>>> 시험점수.loc[(2,1)]

  • 3개 이상의 열을 Multi Index로 만들어 조회도 가능하다.
  • 이번에는 학년, 반, 이름으로 Multi Index를 만들어보자.
>>> 시험점수.reset_index(drop=False, inplace=True)
>>> 시험점수.set_index(["학년", "반", "이름"], inplace=True)
>>> 시험점수

  • 이번에는 2학년 1반의 영철을 조회해보도록 하자.
>>> 시험점수.loc[(2,1,"영철")]
성별     남자
국어    100
수학     80
영어     75
사회     80
과학     45
Name: (2, 1, 영철), dtype: object
  • 위와 같은 방법을 통해 내가 원하는 Column을 Index로 만들어 조회해보았다.
  • 다만, 해당 방법의 한계점은 특정한 하나의 값만 조회가 가능하므로, 수학 점수와 같은 연속형 데이터의 범위에 대해서는 조회가 불가능하다는 단점이 있다.
  • 물론, 이용해 우회하여 해결할 수 있기는 하다.

 

 

 

 

2.6. 새로운 열을 만들어 조회하기

  • 지금까지는 기존에 존재하는 열을 그대로 Index로 만들어 조회를 하였다.
  • 이번에는 내가 필요로 하는 값에 대한 변수를 만들고, 이 변수를 Index로 만들어 조회를 해보도록 하겠다.
  • 수학 점수가 70점 이상인 사람들을 표시하는 새로운 변수를 만들어보자.
    • 조건을 이용한 새로운 변수 만들기는 추후 자세히 다룰 테니 이번엔 단순 참고용으로 보도록 하자.
>>> 시험점수["수학_mask"] = np.where(시험점수["수학"] >=70, "수학_70점_이상", "수학_70점_미만")
>>> 시험점수

  • 70점 이상은 "70점_이상", 70점 미만은 "70점_미만"인 수학_mask"라는 변수를 생성하였다.
  • 새로 만든 "수학_mask"변수를 이용해서 70점 이상인 사람들만 출력해보도록 하자.
>>> 시험점수.set_index("수학_mask", inplace=True)
>>> 시험점수.loc["수학_70점_이상"]

  • 이미지가 너무 커서 일부만 표시해보았다. 길이가 48인 결과가 나왔다면, 제대로 나온 것이다.
>>> len(시험점수.loc["수학_70점_이상"])
48
  • 이번엔 영어 점수가 70점 이상인 사람을 추가해보자.
>>> 시험점수.reset_index(drop=False, inplace=True)
>>> 시험점수["영어_mask"] = np.where(시험점수["영어"] >=70, "영어_70점_이상", "영어_70점_미만")
>>> 시험점수

  • 이번엔 수학 점수가 70점 이상이면서, 영어 점수도 70점 이상인 사람을 출력해보자.
  • Multi Indexing을 사용할 수도 있으나, 이번에는 수학_mask와 영어_mask를 하나로 합쳐서 Index를 만들어보겠다.
>>> 시험점수["key"] = 시험점수["수학_mask"] + "&"+ 시험점수["영어_mask"]
>>> 시험점수.set_index("key", inplace=True)
>>> 시험점수.loc["수학_70점_이상&영어_70점_이상"]

  • 이미지가 너무 커서 일부만 표현하였으며, 길이가 35가 나오면 정상이다.
>>> len(시험점수.loc["수학_70점_이상&영어_70점_이상"])
35
  • 위와 같은 새로운 변수를 만들어 Index로 바꾸는 방법은 이해를 돕기 위해 변수를 지저분하게 만들었지만, 이보다 깔끔하게 할 수 있는 방법을 다음에 포스팅해보도록 하겠다.

 

 

 

지금까지 Index를 이용한 데이터 프레임 행 조회 방법에 대해 알아보았다. 다음 포스트에서는 위치를 이용한 행 조회에 대해 알아보도록 하겠다.

728x90
반응형
728x90
반응형

리눅스 서버에 CUDA 세팅하기

 이전 포스팅에서 우분투 서버에 주피터 노트북을 세팅하는 방법에 대해 알아보았다. 이번 포스트에서는 우분투 서버에 CUDA를 세팅하고, Tensorflow를 이용해 CUDA 세팅이 제대로 되었는지 확인하는 방법에 대해 알아보도록 하겠다.

 

 

 

 

1. Tensorflow가 GPU를 정상적으로 인식하고 있는지 확인

  • Tensorflow를 설치하였더라도, GPU를 사용하기 위해서는 사용하고자 하는 Tensorflow 버전에 맞는 CUDA와 Cudnn을 설치해야 한다.
  • Tensorflow가 현재 인지하고 있는 사용 가능한 CPU, GPU를 확인하는 방법은 다음과 같다.
from tensorflow.python.client import device_lib
device_lib.list_local_devices()

  • 위 사진을 보면, JSON 형태로 CPU와 GPU가 출력된다. 만약 CPU와 GPU가 1개 이상인 경우 "/device:CPU:0", "/device:CPU:1", "/device:CPU:2",..., "/device:GPU:0", "/device:GPU:1", "/device:GPU:2", ...와 같은 식으로 N개의 해당하는 device와 기종 설명 등 간략한 정보가 출력되게 된다.
  • 해당 번호를 기반으로 Tensorflow 사용 시, 원하는 장비를 선택할 수 있다.

 

 

 

 

2. 필요한 CUDA와 cuDNN 버전 확인하기

  • CUDA와 cuDNN을 설치하기 전에 자신이 사용하려고 하는 Tensorflow 버전을 구체적으로 알아야한다.
  • 프로그래밍에 익숙하지 않은 사람이라면, 최신 라이브러리일수록 좋은 것이라고 단순하게 생각할 수 있는데, 최신 버전은 알려지지 않은 오류가 존재할 가능성이 높은 안정되지 않은 상태일 수 있기 때문에, 이러한 이슈를 알아보고 설치하는 것을 추천한다.
  • 이번 포스트에서는 tensorflow-2.4.0을 사용할 것이므로, 그에 해당하는 CUDA와 cuDNN을 설치하도록 하겠다.
  • Tensorflow 버전별 CUDA와 cuDNN 정보는 Tensorflow 공식 홈페이지를 보면 알 수 있다.
  • https://www.tensorflow.org/install/source
 

소스에서 빌드  |  TensorFlow

소스에서 TensorFlow pip 패키지를 빌드하고 Ubuntu Linux 및 macOS에 설치합니다. 명령어는 다른 시스템에도 적용될 수 있지만, Ubuntu 및 macOS용으로만 테스트되었으며 지원됩니다. 참고: 잘 테스트되고

www.tensorflow.org

  • 해당 사이트를 보면, tensorflow-2.4.0에 해당하는 GPU는 cuDNN 8.0, CUDA 11.0임을 알 수 있다.

 

 

 

 

3. CUDA 다운로드하기

  • CUDA와 cuDNN 다운 및 설치 방법은 이전 포스트 "텐서플로우(Tensorflow)와 tensorflow-gpu 설치방법"과 매우 유사하며, 이번에는 리눅스 버전으로 다운을 받아보도록 하자.
  • 자신이 원하는 버전에 해당하는 CUDA를 다운로드하기 위해선 아래 홈페이지로 이동하면 된다.
  • https://developer.nvidia.com/cuda-toolkit-archive
 

CUDA Toolkit Archive

Previous releases of the CUDA Toolkit, GPU Computing SDK, documentation and developer drivers can be found using the links below. Please select the release you want from the list below, and be sure to check www.nvidia.com/drivers for more recent production

developer.nvidia.com

  • 해당 웹사이트에서 CUDA Toolkit 11.0을 클릭하도록 하자.

  • 이번에 Tensorflow를 세팅하고자 하는 서버는 오프라인 우분투 18.04이므로, 다음과 같은 버튼을 클릭하여 설치 파일을 받도록 하자.

  • 위와 같이 누르는 경우, 우리에게 익숙한 파일 다운로드 버튼이 아닌 리눅스 환경에서 파일을 다운로드할 수 있는 명령어가 출력된다.

  • 위 명령어에서 첫 줄인 "wget ~"은 CUDA 설치 파일을 다운로드하는 것이며, 두 번째 줄은 해당 파일을 실행하는 것이다.
  • 인터넷이 되는 우분투 환경으로 이동하여, 이전에 만들어놨던 Download 디렉터리로 이동해서 해당 파일을 다운로드하도록 하자.
wget http://developer.download.nvidia.com/compute/cuda/11.0.2/local_installers/cuda_11.0.2_450.51.05_linux.run

  • 다운 완료 후, 파일이 Download 디렉터리에 설치 파일이 존재하는 것을 확인하도록 하자.
ll

  • 성공적으로 CUDA 11.0 설치 파일이 다운된 것을 볼 수 있다.

 

 

 

 

4. cuDNN 다운로드하기

 

cuDNN Archive

NVIDIA cuDNN is a GPU-accelerated library of primitives for deep neural networks.

developer.nvidia.com

  • cuDNN을 보면, CUDA 버전에 맞는 것이 무엇인지 볼 수 있다.
  • cuDNN은 CUDA Deep Neural Network의 약자로, 딥러닝 관련 연산을 빠르게 해주는 라이브러리들이 모여있는 패치 파일이라고 생각하면 된다.
  • cuDNN 파일은 windows와 마찬가지로 CUDA 설치 후, 압축을 푼 cuDNN 파일을 설치된 CUDA 파일에 덮어 씌워주면 된다.
  • 설치하고자 하는 운영체제에 맞는 cuDNN을 다운로드하도록 하자. 우분투는 단순하게 Linux로 다운로드하여도 상관없다.
  • cuDNN은 CUDA와 달리 NVIDIA 홈페이지 로그인이 필요하므로, 해당 작업 진행 후, cuDNN을 받도록 하자.

  • 로그인 후, 해당 버튼을 누르면 "cudnn-11.0-linux-x64-v8.0.5.39.tar" 또는 뒤에 ".solitairetheme8" 라고 붙은 파일이 다운로드하여진다. 이 경우 윈도우 다운로드 파일에 받아지기 때문에 리눅스로 이동시키는 과정이 필요하다.
  • CUDA를 설치하고자 하는 리눅스 서버와 로컬 PC가 FTP가 뚫려 있는 경우 파일질라(FILEZILLA)와 같은 무료 FTP 클라이언트를 이용하는 것이 가장 직관적인 방법이며, USB를 이용하여 파일을 이동시킬 수도 있다.
  • 해당 포스트는 wsl2를 이용하여 해당 과정을 진행하므로, c드라이브에 "Linux_mv"라는 이름의 임의의 디렉터리를 생성하고, cuDNN 파일을 이동시켰다.
cd /mnt/c/Linux_mv
mv cudnn-11.0-linux-x64-v8.0.5.39.tgz /home/gooopy123/Download
cd
cd Download
ll

 

 

 

 

 이제 CUDA 설치를 위한 기본적인 준비가 되었다. 다음 포스트에서는 CUDA 설치를 진행해보도록 하겠다.

728x90
반응형
728x90
반응형

중심경향치(Center Tendency)

 이전 포스트에서는 최빈값, 중앙값을 구하는 방법에 대해 알아보았다. 이번 포스트에서는 가장 자주 사용되는 중심경향치인 산술 평균과 기하 평균, 조화 평균에 대해서 알아보도록 하자.

 

 

 

1. 산술 평균(Arithmetic mean)

  • 주어진 수의 합을 수의 개수로 나눈 값인 산술 평균은, 통계학에서 가장 많이 사용되는 중심경향치로, 단순하게 평균(Mean)이라고 부르기도 한다.
  • 중앙값처럼 산술 평균 역시, 양적 변수를 대상으로만 사용할 수 있다.

 

1.1. 산술평균의 정의

  • $N$ 개로 구성된 모집단의 관찰값 $X_1, X_2, X_3, ..., X_N$이 있다고 할 때, 모집단의 평균 $\mu$는 다음과 같이 구한다.

$$\mu = \frac{X_1 + X_2 + X_3 + \cdots X_N}{N} = \frac{1}{N}\sum_{i=1}^{N}X_i$$


  • 통계학에서 같은 값을 구한다고 할지라도 모집단을 대상(모수)으로 하는지, 표본집단을 대상(통계량)으로 하는지에 따라, 공식이 약간 달라지게 된다.
  • 평균은 모수와 통계량 모두 공식이 동일하지만, 사용하는 기호가 $\mu$에서 $\bar{x}$로 달라진다.

 

1.2 모평균과 표본평균이 동일한 이유.

 앞서 산술평균에서 단순하게 모수와 통계량의 공식은 동일하지만, 모수는 $\mu$로 통계량은 $\bar{x}$로 사용하는 기호가 다르다고 했다. 이는, 모집단의 평균과 표본 집단의 평균값이 같다는 소리인데, 어떻게 이 것이 가능한 것일까?

  • 먼저, 표본 집단을 완전 무작위로 추출한다고 가정해보자.
  • 이 무작위 추출은 복원 추출(뽑은 값을 또 뽑을 수 있음)을 가정한다.
  • 복원 추출을 가정한 완전 무작위 표본 추출이므로, 원소들이 뽑히는 사건은 서로 독립이다.

  • 위 증명을 보면, 아주 쉽게 표본평균의 기댓값이 모평균과 같다는 것을 찾아내었다.
  • 중간에 나온 원소가 1개인 표본 집합의 기댓값이 모평균과 같다는 이유는 무엇일까?

  • 위와 같은 이유로, 원소가 1개인 표본평균의 분산이 모집단과 동일하다.
  • 즉, 표본을 무수히 많이 뽑게 된다면, 표본 평균의 기댓값은 모평균과 동일하게 된다.

 

 

 

 

 

2. 파이썬으로 산술 평균 구하기.

2.1. 도수분포표로 평균 구하기

  • 중앙값과 마찬가지로, 도수분포표로 평균을 추정할 수는 있으나, 실제 평균과 일치하지는 않는다.
  • 원시 자료를 가지고 있는 상황이라면, 굳이 도수분포표를 구하고, 부정확한 평균을 추론할 필요는 없으나, 혹시나 구할 수 있는 데이터가 도수분포표밖에 없는 상황이 있을 수도 있으므로, 이를 가정하여, 도수분포표로 평균을 구하는 방법에 대해 알아보도록 하겠다.
  • 사용할 데이터와 양적 변수의 범주화된 도수분포표 변환 함수는 다음과 같다.

Data_for_study.csv
3.39MB

import pandas as pd
import numpy as np
def make_freq_table(data, column):
    """
    -------------------------------------------------------------------------------
    지정된 변수의 관찰값이 20개보다 많은 경우, 10개의 등급을 가진 데이터를 반환한다.
    10개의 등급은 동일한 간격으로 자른 것이다.
    -------------------------------------------------------------------------------
    Input: DataFrame, str
    Output: DataFrame
    """
    # array 생성
    target_array = data[column].to_numpy()

    # class의 수가 20개보다 많은 경우 10개로 줄인다.
    class_array = np.unique(target_array)

    if len(class_array) > 20:

        min_key = class_array.min()
        max_key = class_array.max()
        split_key = np.linspace(min_key, max_key, 10)

        a0 = str(round(split_key[0], 2)) + " 이하"
        a1 = str(round(split_key[0], 2)) + " ~ " + str(round(split_key[1], 2))
        a2 = str(round(split_key[1], 2)) + " ~ " + str(round(split_key[2], 2))
        a3 = str(round(split_key[2], 2)) + " ~ " + str(round(split_key[3], 2))
        a4 = str(round(split_key[3], 2)) + " ~ " + str(round(split_key[4], 2))
        a5 = str(round(split_key[4], 2)) + " ~ " + str(round(split_key[5], 2))
        a6 = str(round(split_key[5], 2)) + " ~ " + str(round(split_key[6], 2))
        a7 = str(round(split_key[6], 2)) + " ~ " + str(round(split_key[7], 2))
        a8 = str(round(split_key[7], 2)) + " ~ " + str(round(split_key[8], 2))
        a9 = str(round(split_key[8], 2)) + " 이상"
        new_index = [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]


        target_array= np.where(target_array <= split_key[0], 0,
                               np.where((target_array > split_key[0]) & (target_array <= split_key[1]), 1,
                                        np.where((target_array > split_key[1]) & (target_array <= split_key[2]), 2,
                                                 np.where((target_array > split_key[2]) & (target_array <= split_key[3]), 3,
                                                          np.where((target_array > split_key[3]) & (target_array <= split_key[4]), 4,
                                                                   np.where((target_array > split_key[4]) & (target_array <= split_key[5]), 5,
                                                                            np.where((target_array > split_key[5]) & (target_array <= split_key[6]), 6,
                                                                                     np.where((target_array > split_key[6]) & (target_array <= split_key[7]), 7,
                                                                                              np.where((target_array > split_key[7]) & (target_array <= split_key[8]), 8, 9)))))))))


    # 도수분포표 생성
    freq_table = pd.DataFrame(pd.Series(target_array).value_counts(), columns = ["freq"])
    freq_table.index.name = column

    freq_table.sort_index(inplace = True)
    freq_table["ratio"] = freq_table.freq / sum(freq_table.freq)
    freq_table["cum_freq"] = np.cumsum(freq_table.freq)
    freq_table["cum_ratio"] = np.round(np.cumsum(freq_table.ratio), 2)
    freq_table["ratio"] = np.round(freq_table["ratio"], 2)

    if "new_index" in locals():
        freq_table.index = new_index
        freq_table.index.name = column

    return freq_table
  • 키에 대하여 범주화된 도수분포표를 뽑아보자.
Rawdata = pd.read_csv("Data_for_study.csv")
make_freq_table(Rawdata, "키")

  • 범주화된 도수분포표를 이용한 평균 계산은 각 등급(Class)의 중앙값에 각 빈도를 곱해 평균을 계산한다.
  • 위와 같이 136 이하, 189.33 이상이 존재하는 경우, 두 등급에 속하는 빈도가 전체에서 차지하는 비중이 매우 작으므로, 단순하게 우리가 알고 있는 136, 189.33에 대하여 빈도를 곱하거나, 이상치로 보고 제거해도 된다.
  • 애초에 도수분포표를 이용해 중심경향치를 계산하는 것은 정확한 값을 얻기 위해서가 아닌, 대략적인 값을 얻기 위해서이므로, 이번에는 저 두 값에 그냥 빈도를 곱하도록 하겠다.
>>> ((136.0*1)+((142.67+136.0)/2*70)+((149.33+142.67)/2*869)+((156.0+149.33)/2*7079)+
    ((162.67+156.0)/2*14131)+((169.33+162.67)/2*14640)+((176.0+169.33)/2*12492)+
    ((182.67+176.0)/2*5118)+((189.33+182.67)/2*1241)+(189.33*107))/55748
    
165.47919010547466
  • 위 코드처럼 값을 하나하나 쓰기 싫은 사람을 위해, pandas의 str 모듈을 사용해 계산하는 방법도 보도록 하겠다.
>>> 키_도수분포표 = make_freq_table(Rawdata, "키")
>>> 키_도수분포표.reset_index(drop=False, inplace=True)

# 1 부터 8번 행까지 처리
>>> 키1_8_array = 키_도수분포표[1:9].키.str.split(" ~ ", expand=True).values.astype("float")
>>> 키1_8_sum = np.sum(((키1_8_array[:,0] + 키1_8_array[:,1])/2) * 키_도수분포표[1:9].freq.to_numpy())

# 0, 9 행 처리
>>> 키0_9_array = 키_도수분포표.loc[[0,9]].키.str.partition(" ")[0].to_numpy().astype("float")
>>> 키0_9_sum = np.sum(키0_9_array * 키_도수분포표.loc[[0,9]].freq.to_numpy())

>>> 평균키 = (키1_8_sum + 키0_9_sum)/키_도수분포표.freq.sum()
>>> 평균키
165.4791901054746
  • 0번행과 9번행은 1~8번행까지와 구간의 패턴이 다르므로, 이를 분리하여, 원하는 값만 추출하여 평균을 구했다.
  • 위에서 숫자를 일일이 복사 붙여넣기한 것과 같은 결과가 나왔다.

 

2.2. 파이썬으로 산술 평균 구하기

  • 원시자료를 가지고 있는 경우, 위 같이 번거로운 과정 없이 아주 편하게 평균을 구할 수 있다.
>>> Rawdata.키.mean()
165.5272655521264

>>> np.mean(Rawdata.키.to_numpy())
165.52726555212743
  • Series.mean()은 pandas 함수로 평균을 구하는 것이다.
  • np.mean(array)는 numpy 함수로 평균을 구하는 것이다.
  • 실제 평균과 위에서 도수분포표를 이용해서 추론한 평균이 꽤 유사한 것을 알 수 있다.

 

 

 

 

 

3. 기타 산술 평균

3.1. 가중 평균(Weighted mean)

  • 서로 다른 집단에 대하여, 다른 가중치를 부여하여 평균을 내는 방법
  • 집단의 크기가 서로 다르거나, 다른 점수 배점을 가질 때, 사용하는 방법이다.
  • 예를 들어, A반은 10명, B반은 40명일 때, A반과 B반의 평균을 동일하게 생각하고 $\bar{X_A}$와 $\bar{X_B}$를 더하고 평균을 내면 안 된다.
  • 이 경우, A반은 전체에서 차지하는 비중인 $\frac{10}{50}$만큼 평균 $\bar{X_A}$ 곱해주고, B반은 전체에서 차지하는 비중인 $\frac{40}{50}$만큼 평균 $\bar{X_B}$에 곱해주고 이 둘을 더해줘야 한다.
  • 즉, A반의 평균이 $\bar{X_A} = 60$이고, B반의 평균이 $\bar{X_B} = 70$이라면, 두 반의 평균은 $\frac{60+70}{2}=65$이 아닌, $0.2\bar{X_A} + 0.8\bar{X_B}= 0.2 * 60 + 0.8 * 70 = 68$점이 된다.

3.2. 절삭 평균(Trimmed mean)

  • 이상치인 극단적으로 크거나 작은 값을 제거하고 평균을 내는 방법이다.
  • 예를 들어, A라는 마을의 실제 평균 월급은 250만 원인데, 그 동네에 빌 게이츠가 살아서 평균 월급이 1,000만 원이 나와버린다면, 그 평균은 실제 데이터가 모여 있는 곳을 보여준다고 할 수 없다.
  • 물론, 이상치를 제거한다면, 이상치가 제거된 것을 집단의 크기에도 반영해야 한다.

 

 

 

 

4. 기하 평균(Geometric mean)

  • 기하 평균은 $n$개의 양수 값을 모두 곱하고, $n$ 제곱근을 해준 것이다.
  • 기하 평균은 지금까지 우리가 다뤘던 변수들과 달리 곱셈으로 계산하는 값에서 사용된다.
  • 변수가 비율로 구성된 경우, 기하 평균을 사용하면 된다.
  • $n$ 개로 구성된 모집단의 관찰 값 $a_1, a_2, a_3, ..., a_n$이 있다고 할 때, 기하 평균은 다음과 같이 구한다.

$$(\prod_{i=1}^{n}a_i)^{\frac{1}{n}} = (a_1\cdot a_2 \cdot a_3\cdots a_n)^{\frac{1}{n}}=\sqrt[n]{a_1\cdot a_2 \cdot a_3\cdots a_n}$$


  • 앞서 보았던 산술평균은 합의 평균이고, 기하 평균은 곱의 평균이다.
  • 기하평균의 대상인 변수의 모든 관찰 값은 양수여야 한다(제곱근을 사용하므로).
  • 로그 항등식을 사용하여 기하평균 공식을 변환시키면, $f(n) = ln x$일 때의 일반화된 f-평균이 만들어진다.

$$ln(a_1\cdot a_2 \cdot a_3\cdots a_n)^{\frac{1}{n}} = \frac{lna_1 + lna_2 + lna3 + \cdots + lna_n}{n}$$


 

4.1 파이썬으로 기하평균 구하기

  • 아래와 같은 데이터가 있다고 가정해보자.
salary = [0.85, 1.1, 1.5, 0.95, 2.0, 1.05]
  • 해당 데이터는 월급 200만 원을 1로 놓고, 6명의 월급의 비율을 본 것이다.
  • 즉, $a_1$은 $200*0.85=170$만원을 월급으로 받고, $a_2$는 $200*1.1=220$만원을 월급으로 받는다는 의미다.
  • 위 데이터의 기하 평균은 다음과 같다.
>>> (0.85*1.1*1.5*0.95*2.0*1.05)**(1/6)
1.187064439031504

 

4.2. Numpy로 구하기

  • 반복문을 사용하는 방법 말고 기하평균을 Numpy로 구하는 방법은 크게 2가지가 있다.
  1. array의 모든 원소를 곱하고, 출력된 스칼라에 n 제곱근 씌우기
  2. array를 모두 로그 치환하고, 그에 대한 산술 평균을 구한 후, 지수로 원 상태로 만들어주기

4.2.1. 방법 1.

# 방법 1.
>>> salary_A = np.array(salary)
>>> np.prod(salary_A)**(1/len(salary_A))
1.187064439031504
  • np.prod(array): 배열의 모든 인자들을 곱한다.

4.2.2. 방법 2.

  • 자연로그의 평균을 구하고, 아래 로그의 성질을 사용하여, 기하 평균을 구하는 방법도 있다.

$$\log_xy = A \rightarrow x^A = y$$

# 방법 2
>>> np.exp(1) ** (np.mean(np.log(salary_A)))
1.1870644390315037
  • np.exp(1): 자연로그의 밑인 2.718281...이다.
  • np.log(array): array의 모든 원소를 자연로그로 변환시킨다.

 

4.3. statistics 라이브러리 사용하기

>>> import statistics as stat
>>> stat.geometric_mean(salary_A)
1.187064439031504
  • statistics 라이브러리는 굉장히 많은 수학적 통계 계산 함수를 제공한다.
  • 개인적으로는 statistics 함수를 사용하기보다는 numpy가 성능이 더 좋으므로, numpy를 사용하길 추천한다.

 

 

 

 

 

5. 조화 평균(Harmonic mean)

  • 조화 평균은 주어진 값들의 역수의 산술 평균의 역수다.
  • 평균 변화율을 구할 때 주로 사용한다.
  • $n$ 개로 구성된 모집단의 관찰 값 $a_1, a_2, a_3, ..., a_n$이 있다고 할 때, 조화 평균은 다음과 같이 구한다.

$$H = \frac{n}{\frac{1}{a_1} + \frac{1}{a_2} + \frac{1}{a_3} + \cdots + \frac{1}{a_n}}$$


  • 조화평균은 속도와 같은 변화율의 평균을 구할 때, 매우 유용하다.
  • 예를 들어, 전체 거리의 절반은 40km/h로 이동하고, 나머지 절반을 60km/h로 이동하였다면, 평균 속력은 산술 평균인 50km/h가 아닌 조화 평균인 48km/h가 된다.
  • 동일한 거리에 대하여 소모된 시간이 다르므로, 단순하게 산술 평균을 구한다면, 평균 속력을 구할 수 없다.
  • 위와 같은 경우엔, 시간의 차원에서 평균을 구해야하므로, 시간에 대하여 값을 바꿔주고, 평균을 구한 후, 다시 속력으로 바꿔보자.
  • 거리를 $S$라 가정하고, 속도를 $V$라고 가정 하자.

 

5.1. 변화율(Rate of change)

  • 두 변수의 변화 정도를 비율로 나타낸 것이다.
  • 미분에서 $\frac{dy}{dx}$라는 단어가 나오는데, 이는 변수 $x$에 대한 변수 $y$의 변화율을 의미한다.
  • 평균변화율(Average rate of change):
    어떤 함수 $f(x)$가 있다고 할 때, 두 점 $A(a, f(a)), B(b, f(b))$에 대하여(단, $a<b$이다.), 두 점을 이어 만든 직선의 기울기를 말한다.
  • 함수 $y=f(x)$에서 $x$의 값이 $a$부터 $a + \bigtriangleup x $까지 변한다고 하면, 아래 식을 $[a, a+\bigtriangleup x]$에서의 $y$의 평균변화율이라 한다.

$$\frac{\bigtriangleup y}{\bigtriangleup x} = \frac{f(a+\bigtriangleup x) - f(a)}{\bigtriangleup x}$$

 

5.2. 파이썬으로 조화 평균을 구해보자

V_array = np.array([20, 30, 50, 30, 60, 40, 80]) 
  • 다음과 같은 속도 데이터가 있다고 가정해보자.
  • 조화 평균을 사용하여, 평균 속력을 구해보자.

5.2.1.  Numpy로 구해보자.

  • 조화 평균은 역수 평균의 역수이므로, numpy의 Broadcast를 사용하면 쉽게 구할 수 있다.
>>> 1/(np.mean(1/V_array))
36.68122270742358

5.2.2. statistics 라이브러리 사용하기

>>> stat.harmonic_mean(V_array)
36.68122270742358

 

 

 

 

 

6. 기하 평균과 조화 평균의 주의사항

  • 0인 표본 값이 존재하는 경우, 사용할 수가 없다.
    • 기하평균은 곱의 평균이므로, 원소에 0이 있는 경우 곱하여 0이 된다.
    • 조화 평균은 역수의 평균의 역수인데, 0의 역수는 무한대로 발산한다.
  • 표본 값이 모두 양수여야 한다.
    • 기하평균은 비율, 배수에 대한 것인데, 음수가 나올 수가 없다.
    • 조화수열의 대상인 변화율의 음수는 벡터로써 방향이 반대를 의미한다.
  • 변수가 비율 혹은 배수이지만, 각 표본 값이 독립적일 때 사용할 수 있다.
    • 표본 값끼리 곱했을 때, 어떠한 의미도 갖지 않아야 서로 독립적이라 할 수 있다.

 

728x90
반응형
728x90
반응형

중심경향치(Center Tendency)


◎ 중심경향치: 관찰된 자료들이 어디에 집중되어 있는지를 나타낸다.


 통계학은 기본적으로 데이터가 어디에 모이고, 얼마나 흩어지는지를 통해서 데이터의 성격을 파악한다. 중심경향치는 데이터가 어디에 모이는지를 보는 것으로, 최빈값, 중앙값, 평균 등의 다양한 지표를 이용하여, 데이터가 모인 곳을 찾아낸다.

 그렇다면, 그 데이터가 모이고 흩어진다는 것이 대체 무슨 말일까? 이를 알기 위해, 이전 포스트에서 학습했던 내용을 바탕으로 이를 눈으로 확인해보자.

 

 

 

 

0. 데이터가 모이고 흩어진다는 것은 무엇일까?

  • 이전 포스트에서 사용했던 데이터를 가지고 오고, 모든 변수들을 히스토그램으로 시각화해보자.

Data_for_study.csv
3.39MB

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
Rawdata = pd.read_csv("Data_for_study.csv")
plt.rc('font', family='NanumGothic')
Rawdata.hist(figsize=(16,25), layout=(5,3), grid=False,density=True)

plt.show()

  • 위 코드는 pandas와 matplotlib.pyplot 두 가지를 사용하여, DataFrame 내에 있는 변수들을 시각화해주며, 히스토그램은 그 변수가 무엇이든 간에, 그 변수의 빈도를 이용해 그래프를 그린다. 또한, bins 파라미터를 통해, 키, 몸무게 같은 비율 척도는 실제 형태보다 단순화시켜, 모든 변수들의 추이를 쉽게 파악할 수 있다.
  • 위 히스토그램들을 보면, "주중_인터넷이용시간"은 주로 0 ~ 100 사이에 가장 많은 데이터가 모여 있으며, 흩어진 정도는 그리 크지 않다는 것을 알 수 있다.
  • 이러한 변수별 데이터가 어디에 모여있는지를 하나의 값으로 확인할 수 있는 방법이 바로 중심경향치다.

 

 

 

 

1. 최빈값(Mode)


◎ 최빈값(Mode): 빈도수가 가장 큰 관찰값


  • 도수분포표에서 가장 값이 많이 모여 있는 관찰 값을 의미한다.
  • 양적 자료, 질적 자료에서 모두 사용되나, 일반적으로 질적 자료에서 더 자주 사용된다.
  • 위에서 불러온 데이터에서 명목변수: "흡연경험", 등간변수(리커트 5점 척도): "스트레스인지", 비율 변수: "몸무게"에 대하여 최빈값을 구해보자.

 

1.1. 도수분포표를 사용하여 최빈값 구하기

  • 최빈값을 구하는 방법은 도수분포표를 구하고, 가장 빈도수가 높은 관찰 값을 선택하는 방법이 있다.
  • 연속형 변수를 구간 화하는 것은 꽤 귀찮은 일이므로, 20개 이상의 관찰 값을 갖는 경우, 10개의 구간을 갖는 변수로 변환하여 도수분포표를 출력하는 함수를 만들었다.
def make_freq_table(data, column):
    """
    -------------------------------------------------------------------------------
    지정된 변수의 관찰값이 20개보다 많은 경우, 10개의 등급을 가진 데이터를 반환한다.
    -------------------------------------------------------------------------------
    Input: DataFrame, str
    Output: DataFrame
    """
    # array 생성
    target_array = data[column].to_numpy()

    # class의 수가 20개보다 많은 경우 10개로 줄인다.
    class_array = np.unique(target_array)

    if len(class_array) > 20:

        min_key = class_array.min()
        max_key = class_array.max()
        split_key = np.linspace(min_key, max_key, 10)

        a0 = str(round(split_key[0], 2)) + " 이하"
        a1 = str(round(split_key[0], 2)) + " ~ " + str(round(split_key[1], 2))
        a2 = str(round(split_key[1], 2)) + " ~ " + str(round(split_key[2], 2))
        a3 = str(round(split_key[2], 2)) + " ~ " + str(round(split_key[3], 2))
        a4 = str(round(split_key[3], 2)) + " ~ " + str(round(split_key[4], 2))
        a5 = str(round(split_key[4], 2)) + " ~ " + str(round(split_key[5], 2))
        a6 = str(round(split_key[5], 2)) + " ~ " + str(round(split_key[6], 2))
        a7 = str(round(split_key[6], 2)) + " ~ " + str(round(split_key[7], 2))
        a8 = str(round(split_key[7], 2)) + " ~ " + str(round(split_key[8], 2))
        a9 = str(round(split_key[8], 2)) + " 이상"
        new_index = [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]


        target_array= np.where(target_array <= split_key[0], 0,
                               np.where((target_array > split_key[0]) & (target_array <= split_key[1]), 1,
                                        np.where((target_array > split_key[1]) & (target_array <= split_key[2]), 2,
                                                 np.where((target_array > split_key[2]) & (target_array <= split_key[3]), 3,
                                                          np.where((target_array > split_key[3]) & (target_array <= split_key[4]), 4,
                                                                   np.where((target_array > split_key[4]) & (target_array <= split_key[5]), 5,
                                                                            np.where((target_array > split_key[5]) & (target_array <= split_key[6]), 6,
                                                                                     np.where((target_array > split_key[6]) & (target_array <= split_key[7]), 7,
                                                                                              np.where((target_array > split_key[7]) & (target_array <= split_key[8]), 8, 9)))))))))


    # 도수분포표 생성
    freq_table = pd.DataFrame(pd.Series(target_array).value_counts(), columns = ["freq"])
    freq_table.index.name = column

    freq_table.sort_index(inplace = True)
    freq_table["ratio"] = freq_table.freq / sum(freq_table.freq)
    freq_table["cum_freq"] = np.cumsum(freq_table.freq)
    freq_table["cum_ratio"] = np.round(np.cumsum(freq_table.ratio), 2)
    freq_table["ratio"] = np.round(freq_table["ratio"], 2)

    if "new_index" in locals():
        freq_table.index = new_index
        freq_table.index.name = column

    return freq_table
  • np.linspace(start, end, num): start부터 end까지 num개를 일정한 간격으로 자르는 함수로, 아주 간편하게, 연속형 데이터를 범주화할 수 있다.
  • 흡연경험의 도수분포표
make_freq_table(Rawdata, "흡연경험")

  • 명목 변수인 흡연경험의 최빈값은 1.0인 것을 알 수 있다. 청소년건강행태조사 이용지침서 참고 시, "없다"가 88%로 가장 많이 등장하였다.
make_freq_table(Rawdata, "스트레스인지")

  • 등간 변수(리커트 척도)인 스트레스인지의 최빈값은 3.0인 것을 알 수 있다. 청소년건강행태조사 이용지침서 참고 시, "조금 느낀다"가 가장 많이 등장하였다.
make_freq_table(Rawdata, "몸무게")

  • 비율 변수인 몸무게에서 제일 많이 등장한 등급(Class)는 45.87 ~ 56.3 kg인 것을 알 수 있다. 표본집단인 중·고등학생 중 36%가 해당 구간에 존재한다.

 

1.2. 파이썬을 이용하여 최빈값 구하기

  • 도수분포표를 일일이 구하고, 최빈값을 구하는 일은 꽤 번거로운 일이다.
  • 데이터 분석에서 기본적으로 사용되는 라이브러리 중 하나인 pandas는 다양한 기본 함수를 제공하여, 이러한 문제를 쉽게 해결할 수 있게 해 준다.
  • Series.mode(): 최빈값을 출력한다.
>>> 흡연경험_최빈값 = Rawdata.흡연경험.mode()
>>> 흡연경험_최빈값
0    1.0
dtype: float64


>>> 스트레스인지_최빈값 = Rawdata.스트레스인지.mode()
>>> 스트레스인지_최빈값
0    3.0
dtype: float64


>>> 몸무게_최빈값 = Rawdata.몸무게.mode()
>>> 몸무게_최빈값
0    60.0
dtype: float64
  • Series.mode()는 Series로 결과를 출력한다.
  • 양적 변수라 할지라도, 바로 최빈값을 찾는 경우, 굳이 도수분포표를 만드는 수고를 할 필요가 없으므로, 구간을 만드는 수고를 하지 않아도 된다.
  • 이번에는, 최빈값에 해당하는 빈도수를 출력해보자.
# 최빈값과 최빈값 도수 출력
def mode_value_printer(data, column):
    
    mode_ = data[column].mode().values[0]
    freq =len(data[data[column] == mode_])
    
    print(f"{column} - 최빈값: {mode_}, 도수: {freq}")
>>> mode_value_printer(Rawdata, "흡연경험")
흡연경험 - 최빈값: 1.0, 도수: 48995

>>> mode_value_printer(Rawdata, "스트레스인지")
스트레스인지 - 최빈값: 3.0, 도수: 22915

>>> mode_value_printer(Rawdata, "몸무게")
몸무게 - 최빈값: 60.0, 도수: 2350
  • 보시다시피 pandas 기본 함수를 사용하면, 아주 쉽게 최빈값과 그에 해당하는 도수를 찾을 수 있다.
  • 그러나, 양적 변수, 그중에서도 관찰 값이 매우 많은 변수는 범주화를 시키는 것과, 단순하게 가장 많이 등장한 관찰 값을 찾는 것이 다른 결과를 가져온다.
  • 때문에 양적 변수에서는 중심경향치를 확인하고자 할 때, 최빈값보다는 평균, 중위수와 같은 다른 값을 추출하는 경우가 더 많다(물론, 연구자의 의도에 따라 최빈값 역시 필요할 수 있으므로, 절대 양적 변수에 최빈값을 사용하지는 않는다고 생각해선 안된다).

 

 

 

 

2. 중앙값(Median)


◎ 중앙값(Median): 수치로 된 자료를 크기 순서대로 나열할 때, 가장 가운데에 위치하는 관찰값을 말한다.

$$Md = \frac{(n+1)}{2}$$


  • 중앙값은 순서, 일정한 간격을 가지고 있는 양적 변수에 대해서만 사용 가능하며, 말 그대로 한 변수 내 모든 관찰값들의 중앙에 있는 값을 가리킨다.
  • 중앙값에서 이슈라고 할 수 있는 것은 관찰값의 수가 짝수인지 홀수인지로, 아래 예시를 보자.

$$ A = {1, 3, 4, 7, 8, 10, 11, 15, 16}$$

  • 위 예시 같이 집합 내 원소의 수가 홀수인 경우에는 그냥 $\frac{9+1}{2}=5$에 있는 관찰값을 중앙값으로 하면 되지만, 짝수인 경우는 조금 다르다.

$$ B = {2, 4, 6, 7, 9, 10} $$

  • 위 예시 같이 집합 내 원소의 수가 짝수인 경우에는 $\frac{6+1}{2} = 3.5$가 되어, 3.5번째에 있는 관찰값을 중앙값으로 사용해야 하나, 3.5번째 관찰값은 존재할 수 없다.
  • 이 때는, 3번째 관찰값인 6과 4번째 관찰값인 7의 평균을 중앙값으로 사용한다. 즉, $\frac{6+7}{2} = 6.5$가 중앙값이 된다.

 

2.1. 도수분포표를 이용하여 연속형 데이터의 중앙값 구하기

  • 중앙값은 관찰값들의 중앙에 있는 값이므로, 도수분포표를 사용하지 않고 구할 수 있고, 그것이 정석이다.
  • 그러나, 항상 모든 관찰값들을 알 수 있는 것이 아니고, 때에 따라서는 도수분포표를 사용해서 중앙값을 유추해야할 필요도 있다.
    (물론, 원시자료를 손 델 수 있다면, 굳이 그럴 필요는 없지만!)
  • 이번에는 범주화된 연속형 데이터의 도수분포표를 이용해서 중앙값을 구해보자.
  • 위에서 만든 make_freq_table함수를 이용해서 키에 대한 도수분포표를 만들어보자.
make_freq_table(Rawdata, "키")

  • 총데이터의 양은 55748개이며, 55748의 중앙값은 $\frac{55748+1}{2} = 27874.5$이다. 즉, 27,874.5번째에 있는 값이 있는 구간이 중앙값이다.
  • 누적빈도를 볼 때, 27,874.5는 162.67 ~ 169.33 구간에 존재하므로, 중앙값이 있는 구간은 162.67 ~ 169.33임을 알 수 있다.
  • 이 구간 안에서 비율을 사용해서 중앙값을 유추해보자

  • 위 방법처럼 관찰 값의 비율과 빈도의 비율을 이용하면, 중위수를 유추해낼 수 있다.
  • 실제 중위수랑 비교해보자.
>>> Rawdata.키.median()
165.0
  • 실제 중위수와 도수분포표를 사용해서 유추한 중위수가 상당히 유사한 것을 알 수 있다.

 

2.2. 파이썬을 이용하여 중위수 구하기

  • 파이썬을 이용해 중위수를 구하는 것은 정말 단순하다.
>>> Rawdata.스트레스인지.median()
3.0

>>> Rawdata.몸무게.median()
57.0
  • Series.median()을 사용하면, 중위수를 구할 수 있다.
  • numpy 함수를 사용하는 경우는 다음과 같다.
>>> np.median(Rawdata.스트레스인지.to_numpy())
3.0

>>> np.median(Rawdata.몸무게.to_numpy())
57.0
  • np.median(array)를 사용해서 중위수를 구하면 된다.

 

 

 

 지금까지 최빈값과 중앙값을 구해보았다. 다음 포스트에서는 가장 대표적인 중심경향치인 평균에 대해 자세히 알아보도록 하겠다.

728x90
반응형
728x90
반응형

도수분포표와 시각화

 앞서 도수분포표에서 봤듯, 도수분포표는 수집한 데이터의 분포를 알기 위해 사용한다. 그러나 여전히 숫자만으로 데이터를 파악하기 때문에 데이터의 분포를 명확하게 이해하기 어려울 수도 있다. 도수분포표를 시각화한다면, 보다 쉽게 데이터의 분포를 파악할 수 있다.

  • 도수분포표의 시각화이므로, 들어간 데이터의 도수(빈도)를 시각화 하는 것이다.
  • 파이썬으로 도수분포표를 시각화하는 방법은 크게 두가지가 있다.
  1. 범주화된 데이터를 사용해서 히스토그램을 만들기
  2. 도수분포표 생성 후, 도수분포표를 기반으로 그래프 그리기
  • 개인적으로 추천하는 방법은 도수분포표를 먼저 생성하고, 그래프를 그리는 것으로, 도수분포표 연산이 한 번 이루어지고 나면, 나머지 과정은 자원을 거의 먹지 않는다.
  • 도수분포표만 만들면, 이를 이용해서 히스토그램(막대그래프 사용), 도수분포다각형, 누적도수분포곡선 3가지를 모두 쉽게 그릴 수 있다.

 

 

 

 

1. 히스토그램

  • 히스토그램은 가장 대표적인 도수분포표의 시각화 방법이다.
  • 위에서 설명한 파이썬으로 도수분포표 시각화를 하는 첫 번째 방법으로, 원본 데이터(범주화가 된)를 사용해서 히스토그램을 그리는 것이다.
  • 히스토그램은 막대그래프로 한 변수를 구성하는 각 집단의 빈도를 이용하여, 막대 그래프를 그리는 것이다.
  • 이전 데이터에서 사용했던, 청소년건강행태조사 2019년 데이터에서 일부 변수만 추려낸 데이터를 사용해보자.

Data_for_study.csv
3.39MB

  • "건강인지"를 히스토그램으로 나타내보자.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
Rawdata = pd.read_csv("Data_for_study.csv")
# 한글 사용
plt.rc('font', family='NanumGothic')

# 전체 그래프 크기
fig = plt.figure(figsize=(8, 6))

# 히스토그래프 그리기
plt.hist(Rawdata.건강인지.to_numpy(), bins=9)

# x축 ticks 지정
plt.xticks(np.arange(1, 6), labels=["매우 좋음", "좋음", "보통", "나쁨", "매우 나쁨"])
plt.tick_params(axis="x", direction="in", labelsize = 12, pad = 20)

# title, xlabel, ylabel 지정
plt.title("건강인지 히스토그램", fontsize = 30, pad = 30)
plt.xlabel('건강인지', fontsize = 20, loc = 'center', labelpad = 20)
plt.ylabel('도수', fontsize = 20, rotation = 0, loc='center', labelpad = 30)

plt.show()

  • plt.hist(density=True)로 지정해주면, 빈도가 아닌, 비율로 나타낼 수도 있다.
  • 그러나, 이 비율은 히스토그램 전체를 1로 하는 비율이 아닌, 최대 빈도를 1로 하는 비율이다.
fig = plt.figure(figsize=(8, 6))

plt.hist(Rawdata.건강인지.to_numpy(), bins=9, density=True)
plt.xticks(np.arange(1, 6), labels=["매우 좋음", "좋음", "보통", "나쁨", "매우 나쁨"])
plt.tick_params(axis="x", direction="in", labelsize = 12, pad = 20)

plt.title("건강인지 히스토그램", fontsize = 30, pad = 30)

plt.xlabel('건강인지', fontsize = 20, loc = 'center', labelpad = 20)
plt.ylabel('도수', fontsize = 20, rotation = 0, loc='center', labelpad = 30)


plt.show()

  • 입력된 데이터는 숫자지만, 실제로는 문자인 Factor이므로, label을 문자로 입력하였다.
>>> Rawdata.건강인지.to_numpy()
array([1., 1., 2., ..., 4., 3., 3.])
  • 실제 정의에 맞게 하려면, 다음과 같이 숫자를 원래 형태인 문자로 바꿔주고, 문자 배열에 대한 도수를 히스토그램으로 그려야 한다.
건강인지 = Rawdata.건강인지.to_numpy()
건강인지 = np.where(건강인지==1, "매우 좋음",
                np.where(건강인지==2, "좋음",
                         np.where(건강인지==3, "보통",
                                  np.where(건강인지==4, "나쁨", "매우 나쁨"))))

 

 

 

 

2. 도수분포다각형

  • 도수분포다각형은 히스토그램의 각 중간점을 이어서 그린 것으로, 데이터가 연속적인 경우에 사용한다.
  • 도수분포다각형을 사용하면, 분포의 윤곽이 보다 명확하게 보이며, 빈도의 증감을 보다 명확히 볼 수 있다.
  • 또한, 서로 다른 집단에 대한 도수분포를 같은 그림 위에서 비교할 수 있다는 장점이 있다.
  • 도수분포다각형, 누적도수분포곡선은 도수분포표를 기반으로 그리는 것이 훨씬 쉽다(히스토그램 역시, 이미 생성된 도수분포표를 기반으로 막대그래프로 그리는 것이 추가 연산 시간이 없으므로 쉽다).
  • 이전 포스트에서 만들었던, 16세 남성의 키와 16세 여성의 키를 10cm 간격으로 범주화한 도수분포표를 대상으로 해서 만들어보도록 하자.
  • 총합은 도수분포표를 이해하기 좋게 만든 것이므로, 제거하고, 필요한 각 클래스별 값만 유지하겠다.
def cat_height(array):
    cat_array = np.where(array<=140, "140 이하",
                     np.where((array>140) & (array<=150), "140~150",
                              np.where((array>150) & (array<=160), "150~160",
                                       np.where((array>160) & (array<=170), "160~170",
                                                np.where((array>170) & (array<=180), "170~180",
                                                         np.where((array>180) & (array<=190), "180~190", "190 이상"))))))
    return cat_array
    
    
def Freq_table(array):

    freq_table = pd.DataFrame(pd.Series(array).value_counts(), columns=["freq"])
    freq_table.sort_index(inplace = True)
    freq_table["ratio"] = freq_table.freq / sum(freq_table.freq)
    freq_table["cum_freq"] = np.cumsum(freq_table.freq)
    freq_table["cum_ratio"] = np.round(np.cumsum(freq_table.ratio), 2)
    freq_table["ratio"] = np.round(freq_table["ratio"], 2)

    return freq_table
    
남자_16세_키 = Rawdata[(Rawdata["연령"] == 16) & (Rawdata["성별"] == 1.0)]["키"].to_numpy()
여자_16세_키 = Rawdata[(Rawdata["연령"] == 16) & (Rawdata["성별"] == 2.0)]["키"].to_numpy()

M_DF = Freq_table(cat_height(남자_16세_키))
F_DF = Freq_table(cat_height(여자_16세_키))
fig = plt.figure(figsize=(8, 6))

plt.plot(F_DF.index, F_DF.ratio, label = "Female, 16 years old")
plt.plot(M_DF.index, M_DF.ratio, linestyle = "--", label = "Male, 16 years old")

plt.title("16세 남성 & 여성 키 도수분포다각형",fontsize = 20, pad = 20)
plt.xlabel("키", fontsize = 15)
plt.ylabel("비율", fontsize = 15, rotation = 0, labelpad = 30)
plt.legend(loc="upper right")

plt.show()

  • 생성된 두 집단의 도수분포표를 상대적으로 비교하기 위해, 상대 빈도인 비율을 사용하여 그래프를 그렸다.
  • 도수분포다각형이 히스토그램의 꼭짓점을 연결한 것임을 보기 위해 히스토그램도 뒤에 연하게 그려보자.
fig = plt.figure(figsize=(8, 6))


plt.plot(F_DF.index, F_DF.ratio, label = "Female, 16 years old")
plt.plot(M_DF.index, M_DF.ratio, linestyle = "--", label = "Male, 16 years old")

plt.bar(F_DF.index, F_DF.ratio, alpha = 0.4, color = "blue")
plt.bar(M_DF.index, M_DF.ratio, alpha = 0.4, color = "yellow")

plt.title("16세 남성 & 여성 키 도수분포다각형",fontsize = 20, pad = 20)
plt.xlabel("키", fontsize = 15)
plt.ylabel("비율", fontsize = 15, rotation = 0, labelpad = 30)
plt.legend(loc="upper right")

plt.show()

  • 동일한 비율을 사용하기 위해 plt에서 histogram 함수가 아닌 막대그래프인 bar를 가지고 왔다.
  • 히스토그램과 막대그래프는 본질이 다르지만, 도수분포표를 사용해서 그린다면 히스토그램과 막대그래프는 동일한 결과를 가지고 온다.
    (히스토그램은 데이터가 한 차원만 들어가고, 그 빈도로 그래프를 그린다. 막대그래프는 두 차원의 데이터가 필요하며, 그 두 차원의 데이터를 이용해서 그래프를 그린다)
  • 가지고 있는 전체 데이터를 이용해서 그래프를 그린다면 히스토그램을 사용하고, 이미 도수분포표를 만들었다면, 막대그래프를 그리길 추천한다.

 

 

 

 

3. 누적도수분포곡선

  • 누적도수분포곡선은 위에서 도수분포다각형을 그렸던 방법에서, y축에 들어가는 데이터만 누적 도수로 바꾸면 된다.
  • 이번에도 두 집단을 비교하기 쉽도록, 비율로 구해보도록 하겠다.
fig = plt.figure(figsize=(8, 6))


plt.plot(F_DF.index, F_DF.cum_ratio, label = "Female, 16 years old")
plt.plot(M_DF.index, M_DF.cum_ratio, linestyle = "--", label = "Male, 16 years old")

plt.bar(F_DF.index, F_DF.cum_ratio, alpha = 0.4, color = "blue")
plt.bar(M_DF.index, M_DF.cum_ratio, alpha = 0.4, color = "yellow")

plt.title("16세 남성 & 여성 키 누적도수분포곡선",fontsize = 20, pad = 20)
plt.xlabel("키", fontsize = 15)
plt.ylabel("비율", fontsize = 15, rotation = 0, labelpad = 30)
plt.legend(loc="lower right")

plt.show()

 

 

 

 지금까지 파이썬을 사용해서 도수분포표의 시각화를 해보았다. 개인적으로는 도수분포표를 먼저 구하고, 그 도수분포표를 바탕으로 시각화를 진행하길 바란다.

728x90
반응형

+ Recent posts