참고 서적
도서명: Data Science from Scratch (밑바닥부터 시작하는 데이터 과학) 저자 : Joel Grus 출판 : 프로그래밍 인사이트 |
이 포스팅은 '밑바닥부터 시작하는 데이터과학' 도서를 공부하면서 추가적으로 공부하거나, 새롭게 알게된 내용들을 정리하였습니다.
Contents
- Collections module
- Random module
- re
- Automated Testing and assert
- Data Visualization
1. Collections - Container datatypes
- 1. Counter
- 2. ChainMap
- 3. defaultdict
- 4. OrderedDict
- 5. namedtuple
Collection
파이썬의 collections 모듈은 파이썬에 내장된 일반 자료형의(dict, list, set, tuple) 컨테이너 타입을 조금 더 발전시킨 형태의 구현체이다. 파이썬 2.7 까지는 네임드튜플, 디큐, 카운터, 순서형 딕셔너리, 기본 딕셔너리의 다섯 개의 컨테이너를 구현하고 있었으나 파이썬 3부터는 체인맵, 유저 딕셔너리, 유저 리스트, 유저 스트링 등의 자료구조가 추가되었다. collections 모듈에는 데이터 전처리를 위한 유용한 객체가 많이 있어 알아두면 잘 사용할 수 있다.
1. Counter
Counter은 dict의 subclass로써 새로운 인스턴스를 만드는 class, dict 내 아이템의 발생빈도를 카운트하여 저장한다.
실제 key 값들에 연속된 상황의 확인이 필요할 경우에 주로 사용한다.
Collections 모듈에 있는 defalutdict와 유사한 객체로 변환해주며 키와 값의 빈도를 연결시켜준다.
- 사용 예시
import collections
scores = ['A', 'B', 'B', 'C', 'C', 'D', 'A']
counter = collections.Counter(scores)
print(counter)
# 출력 값
# Counter({'A': 2, 'B': 2, 'C': 2, 'D': 1})
- 계산 연산자
연산자 | 설명 |
-= | 뺀다. 결과가 음수면 그 요소는 삭제된다. |
&= | 좌변의 Counter 객체 요소 중 우변의 Counter 객체 요소에 미포함되어 있는 key의 요소를 삭제한다. 요소의 값은 둘 중 작은 쪽의 값이 된다. |
l= | 2개의 Counter 객체 전체의 요소로부터 새롭게 Counter 객체를 생성한다. key가 같으면 두 값 중 큰 쪽의 값이 된다. |
- 메소드
메소드(method) | 설명 |
update() | Counter의 값 갱신 counter나 문자열 형식 가능 |
elements() | 값의 요소 반환 (무작위 순서) |
most_common(n) | 빈도수가 높은 순으로 tuple 형태로 반환 |
subtract() | 해당하는 요소를 빼주며 0보다 작을 시 음수 |
import collections
scores = ['A', 'B', 'B', 'C', 'C', 'D', 'A']
counter = collections.Counter(scores)
print(counter)
# update()
score2 = ['C','D']
counter.update(score2)
print(counter)
## 결과 ##
Counter({'A': 2, 'B': 2, 'C': 2, 'D': 1})
Counter({'C': 3, 'A': 2, 'B': 2, 'D': 2})
# elements()
list(counter.elements())
## 결과 ##
['A', 'A', 'B', 'B', 'C', 'C', 'C', 'D', 'D']
# most_common()
# 빈도수가 높은 값을 투플 형태로 출력
counter.most_common(2)
## 결과 ##
[('C', 3), ('A', 2)]
# subtract()
counter.subtract(score2)
print(counter)
## 결과 ##
Counter({'A': 2, 'B': 2, 'C': 2, 'D': 1})
2. ChainMap
ChainMap은 여러 딕셔너리 객체를 하나로 모아 통합시켜준다. 통합된 객체에 변화가 발생할 경우, 딕셔너리에도 그 영향이 반영된다.
- 예시
score1 = {'A' : 3, 'B' : 5}
score2 = {'C' : 4, 'D' : 1}
counter = collections.ChainMap(score1,score2)
## 결과 ##
ChainMap({'A': 3, 'B': 5}, {'C': 4, 'D': 1})
Collection module의 ChinMap 를 사용하지 않아도 여러 방법으로 딕셔너리 객체를 하나로 합칠 수 있다.
아래 링크를 참고하자.
https://towardsdatascience.com/merge-dictionaries-in-python-d4e9ce137374
3. defaultdict
존재하지 않는 키를 처리해주는 방법은 다양하다. 하지만 이러한 방법은 복잡하다.
defaultdict를 이용하며면 딕셔너리 key 값이 미등록됐을 때 KeyError가 발생하는 단점을 보완한 객체이며 새로운 인스턴스를 만드는 데 적합하다.
딕셔너리와 defaultdict의 차이점은 만약 존재하지 않는 키가 주어진다면, defaultdict는 이 키와 인자에서 주어진 값으로 dict에 새로운 항목을 추가해준다.
- 예시
# 디폴트 값 선택 가능
e1 = collections.defaultdict(int)
e2 = collections.defaultdict(dict)
e3 = collections.defaultdict(list)
e4 = collections.defaultdict(set)
# default 생성
def get_default_value():
return 'default-value'
# 여기서 get_default_value와 같은 callable 객체나 None을 입력할 수 있다.
# None을 입력할 경우 일반 사전과 마찬가지로 KeyError가 발생한다.
counter = collections.defaultdict(get_default_value, score = 'a')
print(counter['scores'])
## 결과 ##
default-value
4. OrderedDict
순서(시퀀스)를 유지하기 위해 linked list가 내부에 구성되어 각 순서가 유지되는 딕셔너리 (순서 유지)
- 예시
# 순서가 있는 사전
scores = collections.OrderedDict([('h',88), ('s', 97), ('a', 87)])
scores.popitem(last = True) # 마지막 순서 pop / last = False 시 첫번째 순서 pop
## 결과 ##
('a', 87)
scores.move_to_end(key = "h", last = True) # 마지막으로 key : value 옮기기 / last = False 시 첫번쨰로 옮기기
## 결과 ##
OrderedDict([('s', 97), ('h', 88)])
5. namedtuple
Index를 기준으로 접근하는 Tuple에 Key(name)를 가지고 접근할 수 있도록 지원해줌. (tuple의 확장 타입)
- 예시
# name 데이터 관리
score = collections.namedtuple("score", "A, B, C")
data = score(90, 70, 50)
print(data.B)
## 결과 ##
70
References
https://docs.python.org/3/library/collections.html#collections.namedtuple
- 파이썬 collections, headq 모듈 설명 [greeksharifa]
- python collections 모듈 이해하기 [Yong Jun Moon]
- 코딩하는 금융인:티스토리
2. Random module
파이썬에서 랜덤 관련한 함수들을 모아놓은 모듈 입니다.
랜덤 함수들을 사용하기 위해서는 import random을 해야합니다.
import random
이렇게 랜덤을 임포트 하게 되면 이제 random.함수이름() 을 통해서 랜덤 모듈에 존재하는 함수들을 가지고와서 사용할 수 있습니다.
1. random.random()
random.random() 함수는 0.0에서부터 1.0 사이의 실수(float)를 반환합니다. [0.0, 1.0)
정확한 범위는 1.0을 포함하지 않는 범위 입니다. (0.0 <= x < 1.0)
2. random.uniform(a, b)
random.uniform 함수는 인자로 들어온 a~b 사이의 실수(float)를 반환합니다.
uniform 함수의 랜덤 범위는 a <= x <= b 입니다.
3. randint(a, b)
randint 함수는 인자로 들어온 a, b 사이의 랜덤한 정수(int)를 반환합니다.
반환하는 x는 a, b를 포함한 범위 입니다. (a <= N <= b)
randrange 함수에 a, b+1을 넣은것과 동일하게 동작합니다.
x = random.randint(10, 20)
print(x) # 10 <= x <= 20
4. randrange(a, b), randrange(b)
randrange 함수는 매개변수 1개 버전, 2개 버전이 존재합니다.
randrange(a, b)는 a <= x < b 의 범위 내에서의 랜덤한 정수(int)를 반환합니다. b를 포함하지 않는 범위입니다!
randrange(b)는 0 <= x < b 의 범위 내에서의 랜덤한 정수(int)를 반환합니다. b를 포함하지 않습니다!
x1 = random.randrange(10, 20)
print(x1) # 10 <= x < 20 사이의 랜덤한 int 반환
x2 = random.randrange(20)
print(x2) # 0 <= x < 20 사이의 랜덤한 int 반환
5. random.choice(seq)
choice 함수는 매개변수로 seq 타입을 받습니다. 시퀀스 데이터 타입은 문자열, 튜플, range, 리스트 타입들을 말합니다.
시퀀스 함수는 인자로 받은 리스트, 튜플, 문자열, range 에서 무작위로 하나의 원소를 뽑습니다.
만약, 비어있는 시퀀스 타입의 객체를 인자로 넣는다면 (ex. 리스트가 비어있다면) indexError 의 예외가 발생합니다.
(indexError : 시퀀스의 인덱스가 범위를 벗어났을때 발생하는 에러)
x1 = random.choice('BlockDMask')
print(x1) # 'BlockDMask' 문자열중 랜덤한 문자를 반환
x2 = random.choice('')
print(x2) # indexError 발생
6. random.sample(seq or set, N)
첫번째 매개 변수로 시퀀스 데이터 타입(튜플, 문자열, range, 리스트) 또는 set 타입을 받을 수 있습니다.
두번째 매개 변수로는 랜덤하게 뽑을 인자의 개수 입니다.
sample 함수는 첫번째 인자로 받은 시퀀스데이터 or set에서 N개의 랜덤하고, unique하고, 순서상관없이 인자를 뽑아서 리스트로 만들어서 반환해줍니다.
sample의 특징 2가지
1. unique라는것이 중요합니다. 겹치지 않는 element를 반환한다는 것인데요
- 'abc' 라는 문자열 있다고 할때 N에 2를 넣을때 겹치지 않게 a,b 혹은 b,c 혹은 c,a를 반환 합니다. (순서 상관없음)
- 'aaa' 라는 문자열이 있다고 할때 N에 2를 넣으면 겹치지 않게 a,a, 혹은 a,a 혹은 a,a 를 반환 합니다. (색으로 구분했습니다, 순서상관없음)
값이 중요한게 아니라 배열의 위치가 겹치지 않게 유니크한 것을 뽑는다는 이야기가 하고 싶었습니다.
다시한번 말씀드리자면, str = 'aaa'가 있을때 str[0],str[1] or str[1],str[2] or str[2],str[0] 을 뽑는다는 것 입니다.
2. 만약에 두번째 매개변수 N이 seq, set의 인자의 개수를 넘어갈때 valueError가 발생합니다.
- 'abc'라는 문자열을 첫번째 인자로 넣고, 두번째 인자 N를 3을 넘어가는 수인 10을 넣었다고 한다면 valueError가 발생하게 됩니다.
arr1 = 'abc'
print(arr1, 2) # ['a','b'] or ['b','c'] or ['c','a'] 순서는 바뀔수있음
arr2 = 'ccc'
print(arr2, 10) # valueError 발생
arr3 = 'ccc'
print(arr3, 3) #['c','c','c'] 반환
7 . random.shuffle(seq)
셔플 함수는 데이터의 순서를 무작위로 랜덤하게 바꾸어 주는 함수 입니다.
매개변수 x에는 시퀀스 데이터 타입이 들어가게 됩니다. 하지만 내부의 값을 무작위로 바꿔야 하기 때문에
내부인자를 변경할 수 있는 리스트만 가능하게 됩니다. (문자열, 튜플 및 range(a,b)는 불가능)
random.suffle(리스트) 의 반환은 없고, 인자로 들어온 리스트 내부의 데이터를 무작위로 섞습니다.
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(arr) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
random.shuffle(arr)
print(arr) # [10, 5, 6, 7, 8, 9, 2, 3, 4, 1] 무작위로 변경됨.
Bookkeeping functions
random.seed(a=None, version=2)
Initialize the random number generator.
If a is omitted or None, the current system time is used. If randomness sources are provided by the operating system, they are used instead of the system time (see the os.urandom() function for details on availability).
If a is an int, it is used directly.
With version 2 (the default), a str, bytes, or bytearray object gets converted to an int and all of its bits are used.
With version 1 (provided for reproducing random sequences from older versions of Python), the algorithm for str and bytes generates a narrower range of seeds.
Changed in version 3.2: Moved to the version 2 scheme which uses all of the bits in a string seed.
Deprecated since version 3.9: In the future, the seed must be one of the following types: NoneType, int, float, str, bytes, or bytearray.
random.getstate()
Return an object capturing the current internal state of the generator. This object can be passed to setstate() to restore the state.
random.setstate(state)
state should have been obtained from a previous call to getstate(), and setstate() restores the internal state of the generator to what it was at the time getstate() was called.
Functions for bytes
random.randbytes(n)
Generate n random bytes.
This method should not be used for generating security tokens. Use secrets.token_bytes() instead.
New in version 3.9.
Functions for integers
random.randrange(stop)random.randrange(start, stop[, step])
Return a randomly selected element from range(start, stop, step). This is equivalent to choice(range(start, stop, step)), but doesn’t actually build a range object.
The positional argument pattern matches that of range(). Keyword arguments should not be used because the function may use them in unexpected ways.
Changed in version 3.2: randrange() is more sophisticated about producing equally distributed values. Formerly it used a style like int(random()*n) which could produce slightly uneven distributions.
Deprecated since version 3.10: The automatic conversion of non-integer types to equivalent integers is deprecated. Currently randrange(10.0) is losslessly converted to randrange(10). In the future, this will raise a TypeError.
Deprecated since version 3.10: The exception raised for non-integral values such as randrange(10.5) or randrange('10') will be changed from ValueError to TypeError.
random.randint(a, b)
Return a random integer N such that a <= N <= b. Alias for randrange(a, b+1).
random.getrandbits(k)
Returns a non-negative Python integer with k random bits. This method is supplied with the MersenneTwister generator and some other generators may also provide it as an optional part of the API. When available, getrandbits() enables randrange() to handle arbitrarily large ranges.
Changed in version 3.9: This method now accepts zero for k.
Functions for sequences
random.choice(seq)
Return a random element from the non-empty sequence seq. If seq is empty, raises IndexError.
random.choices(population, weights=None, *, cum_weights=None, k=1)
Return a k sized list of elements chosen from the population with replacement. If the population is empty, raises IndexError.
If a weights sequence is specified, selections are made according to the relative weights. Alternatively, if a cum_weights sequence is given, the selections are made according to the cumulative weights (perhaps computed using itertools.accumulate()). For example, the relative weights [10, 5, 30, 5] are equivalent to the cumulative weights [10, 15, 45, 50]. Internally, the relative weights are converted to cumulative weights before making selections, so supplying the cumulative weights saves work.
If neither weights nor cum_weights are specified, selections are made with equal probability. If a weights sequence is supplied, it must be the same length as the population sequence. It is a TypeError to specify both weights and cum_weights.
The weights or cum_weights can use any numeric type that interoperates with the float values returned by random() (that includes integers, floats, and fractions but excludes decimals). Weights are assumed to be non-negative and finite. A ValueError is raised if all weights are zero.
For a given seed, the choices() function with equal weighting typically produces a different sequence than repeated calls to choice(). The algorithm used by choices() uses floating point arithmetic for internal consistency and speed. The algorithm used by choice() defaults to integer arithmetic with repeated selections to avoid small biases from round-off error.
New in version 3.6.
Changed in version 3.9: Raises a ValueError if all weights are zero.
random.shuffle(x[, random])
Shuffle the sequence x in place.
The optional argument random is a 0-argument function returning a random float in [0.0, 1.0); by default, this is the function random().
To shuffle an immutable sequence and return a new shuffled list, use sample(x, k=len(x)) instead.
Note that even for small len(x), the total number of permutations of x can quickly grow larger than the period of most random number generators. This implies that most permutations of a long sequence can never be generated. For example, a sequence of length 2080 is the largest that can fit within the period of the Mersenne Twister random number generator.
Deprecated since version 3.9, will be removed in version 3.11: The optional parameter random.
random.sample(population, k, *, counts=None)
Return a k length list of unique elements chosen from the population sequence or set. Used for random sampling without replacement.
Returns a new list containing elements from the population while leaving the original population unchanged. The resulting list is in selection order so that all sub-slices will also be valid random samples. This allows raffle winners (the sample) to be partitioned into grand prize and second place winners (the subslices).
Members of the population need not be hashable or unique. If the population contains repeats, then each occurrence is a possible selection in the sample.
Repeated elements can be specified one at a time or with the optional keyword-only counts parameter. For example, sample(['red', 'blue'], counts=[4, 2], k=5) is equivalent to sample(['red', 'red', 'red', 'red', 'blue', 'blue'], k=5).
To choose a sample from a range of integers, use a range() object as an argument. This is especially fast and space efficient for sampling from a large population: sample(range(10000000), k=60).
If the sample size is larger than the population size, a ValueError is raised.
Changed in version 3.9: Added the counts parameter.
Deprecated since version 3.9: In the future, the population must be a sequence. Instances of set are no longer supported. The set must first be converted to a list or tuple, preferably in a deterministic order so that the sample is reproducible.
Real-valued distributions
The following functions generate specific real-valued distributions. Function parameters are named after the corresponding variables in the distribution’s equation, as used in common mathematical practice; most of these equations can be found in any statistics text.
random.random()
Return the next random floating point number in the range [0.0, 1.0).
random.uniform(a, b)
Return a random floating point number N such that a <= N <= b for a <= b and b <= N <= a for b < a.
The end-point value b may or may not be included in the range depending on floating-point rounding in the equation a + (b-a) * random().
random.triangular(low, high, mode)
Return a random floating point number N such that low <= N <= high and with the specified mode between those bounds. The low and high bounds default to zero and one. The mode argument defaults to the midpoint between the bounds, giving a symmetric distribution.
random.betavariate(alpha, beta)
Beta distribution. Conditions on the parameters are alpha > 0 and beta > 0. Returned values range between 0 and 1.
random.expovariate(lambd)
Exponential distribution. lambd is 1.0 divided by the desired mean. It should be nonzero. (The parameter would be called “lambda”, but that is a reserved word in Python.) Returned values range from 0 to positive infinity if lambd is positive, and from negative infinity to 0 if lambd is negative.
random.gammavariate(alpha, beta)
Gamma distribution. (Not the gamma function!) Conditions on the parameters are alpha > 0 and beta > 0.
The probability distribution function is:
x ** (alpha - 1) * math.exp(-x / beta)
pdf(x) = --------------------------------------
math.gamma(alpha) * beta ** alpha
random.gauss(mu, sigma)
Normal distribution, also called the Gaussian distribution. mu is the mean, and sigma is the standard deviation. This is slightly faster than the normalvariate() function defined below.
Multithreading note: When two threads call this function simultaneously, it is possible that they will receive the same return value. This can be avoided in three ways. 1) Have each thread use a different instance of the random number generator. 2) Put locks around all calls. 3) Use the slower, but thread-safe normalvariate() function instead.
random.lognormvariate(mu, sigma)
Log normal distribution. If you take the natural logarithm of this distribution, you’ll get a normal distribution with mean mu and standard deviation sigma. mu can have any value, and sigma must be greater than zero.
random.normalvariate(mu, sigma)
Normal distribution. mu is the mean, and sigma is the standard deviation.
random.vonmisesvariate(mu, kappa)
mu is the mean angle, expressed in radians between 0 and 2*pi, and kappa is the concentration parameter, which must be greater than or equal to zero. If kappa is equal to zero, this distribution reduces to a uniform random angle over the range 0 to 2*pi.
random.paretovariate(alpha)
Pareto distribution. alpha is the shape parameter.
random.weibullvariate(alpha, beta)
Weibull distribution. alpha is the scale parameter and beta is the shape parameter.
Alternative Generator
class random.Random([seed])
Class that implements the default pseudo-random number generator used by the random module.
Deprecated since version 3.9: In the future, the seed must be one of the following types: NoneType, int, float, str, bytes, or bytearray.
class random.SystemRandom([seed])
Class that uses the os.urandom() function for generating random numbers from sources provided by the operating system. Not available on all systems. Does not rely on software state, and sequences are not reproducible. Accordingly, the seed() method has no effect and is ignored. The getstate() and setstate() methods raise NotImplementedError if called.
Notes on Reproducibility
Sometimes it is useful to be able to reproduce the sequences given by a pseudo-random number generator. By re-using a seed value, the same sequence should be reproducible from run to run as long as multiple threads are not running.
Most of the random module’s algorithms and seeding functions are subject to change across Python versions, but two aspects are guaranteed not to change:
- If a new seeding method is added, then a backward compatible seeder will be offered.
- The generator’s random() method will continue to produce the same sequence when the compatible seeder is given the same seed.
References
- https://blockdmask.tistory.com/383
- https://wikidocs.net/106795
- https://docs.python.org/3/library/random.html?highlight=random#module-random
3. Regular Expression
1. 정규 표현식 (Regular Expression)
정규 표현식은 특정한 규칙을 가진 문자열의 패턴을 표현하는 데 사용하는 표현식(Expression)으로 텍스트에서 특정 문자열을 검색하거나 치환할 때 흔히 사용된다. 예를 들어, 웹페이지에서 전화번호나 이메일 주소를 발췌한다거나 로그파일에서 특정 에러메시지가 들어간 라인들을 찾을 때 정규 표현식을 사용하면 쉽게 구현할 수 있다. 정규 표현식은 간단히 정규식, Regex 등으로 불리우곤 한다.
2. 정규 표현식 사용
정규식에서 가장 단순한 것은 특정 문자열을 직접 리터럴로 사용하여 해당 문자열을 검색하는 것이다. 예를 들어, 로그 파일에 "에러 1033" 이라는 문자열을 검색하여 이 문자열이 있으면 이를 출력하고 없으면 None을 리턴하는 경우이다. 이러한 간단한 검색을 파이썬에서 실행하는 방법은 아래와 같다.
먼저 파이썬에서 정규표현식을 사용하기 위해서는 Regex를 위한 모듈인 re 모듈을 사용한다.
re 모듈의 compile 함수는 정규식 패턴을 입력으로 받아들여 정규식 객체를 리턴하는데, 즉 re.compile(검색할문자열) 와 같이 함수를 호출하면 정규식 객체 (re.RegexObject 클래스 객체)를 리턴하게 된다.
re.RegexObject 클래스는 여러 메서드들을 가지고 있는데, 이 중 여기서는 특정 문자열을 검색하여 처음 맞는 문자열을 리턴하는 search() 메서드를 사용해 본다. 이 search() 는 처음 매칭되는 문자열만 리턴하는데, 매칭되는 모든 경우를 리턴하려면 findall() 을 사용한다. search()는 검색 대상이 있으면 결과를 갖는 MatchObject 객체를 리턴하고, 맞는 문자열이 없으면 None 을 리턴한다.
MatchObject 객체로부터 실제 결과 문자열을 얻기 위해서는 group() 메서드를 사용한다.
import re
text = "에러 1122 : 레퍼런스 오류\n 에러 1033: 아규먼트 오류"
regex = re.compile("에러 1033")
mo = regex.search(text)
if mo != None:
print(mo.group())
3. 전화번호 발췌하기
정규 표현식은 단순한 리터럴 문자열을 검색하는 것보다 훨씬 많은 기능들을 제공하는데, 즉 특정 패턴의 문자열을 검색하는데 매우 유용하다. 그 한가지 예로 웹페이지나 텍스트에서 특정 패턴의 전화번호를 발췌하는 기능에 대해 알아보자. 전화번호의 패턴은 032-232-3245 와 같이 3자리-3자리-4자리로 구성되어 있다고 가정하자. 정규식에서 숫자를 의미하는 기호로 \d 를 사용한다. 여기서 d는 digit 을 의미하고 0 ~ 9 까지의 숫자 중 아무 숫자나 될 수 있다. 따라서, 위 전화번호 패턴을 정규식으로 표현하면 \d\d\d-\d\d\d-\d\d\d\d 와 같이 될 수 있다. 아래는 이러한 패턴을 사용하여 전화번호를 발췌하는 예이다.
import re
text = "문의사항이 있으면 032-232-3245 으로 연락주시기 바랍니다."
regex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
matchobj = regex.search(text)
phonenumber = matchobj.group()
print(phonenumber)
위 예제에서 re.compile(전화번호패턴) 함수는 전화번호 패턴에 갖는 정규식 객체를 리턴하게 되고, search()를 사용하여 첫번째 전화번호 패턴에 매칭되는 번호를 리턴한다. 그리고 이로부터 실제 전화번호를 얻기 위해서는 group() 메서드를 사용하였다.
4. 다양한 정규식 패턴 표현
위의 전화번호 예제에서는 숫자를 표현하는 \d 만을 살펴보았는데, 정규 표현식에는 매우 다양한 문법과 기능들이 제공되고 있다. 아래는 이러한 다양한 정규식 표현 중 자주 사용되는 패턴들을 정리한 것이다.
패턴설명예제
^ | 이 패턴으로 시작해야 함 | ^abc : abc로 시작해야 함 (abcd, abc12 등) |
$ | 이 패턴으로 종료되어야 함 | xyz$ : xyz로 종료되어야 함 (123xyz, strxyz 등) |
[문자들] | 문자들 중에 하나이어야 함. 가능한 문자들의 집합을 정의함. | [Pp]ython : "Python" 혹은 "python" |
[^문자들] | [문자들]의 반대로 피해야할 문자들의 집합을 정의함. | [^aeiou] : 소문자 모음이 아닌 문자들 |
| | 두 패턴 중 하나이어야 함 (OR 기능) | a | b : a 또는 b 이어야 함 |
? | 앞 패턴이 없거나 하나이어야 함 (Optional 패턴을 정의할 때 사용) | \d? : 숫자가 하나 있거나 없어야 함 |
+ | 앞 패턴이 하나 이상이어야 함 | \d+ : 숫자가 하나 이상이어야 함 |
* | 앞 패턴이 0개 이상이어야 함 | \d* : 숫자가 없거나 하나 이상이어야 함 |
패턴{n} | 앞 패턴이 n번 반복해서 나타나는 경우 | \d{3} : 숫자가 3개 있어야 함 |
패턴{n, m} | 앞 패턴이 최소 n번, 최대 m 번 반복해서 나타나는 경우 (n 또는 m 은 생략 가능) | \d{3,5} : 숫자가 3개, 4개 혹은 5개 있어야 함 |
\d | 숫자 0 ~ 9 | \d\d\d : 0 ~ 9 범위의 숫자가 3개를 의미 (123, 000 등) |
\w | 문자를 의미 | \w\w\w : 문자가 3개를 의미 (xyz, ABC 등) |
\s | 화이트 스페이스를 의미하는데, [\t\n\r\f] 와 동일 | \s\s : 화이트 스페이스 문자 2개 의미 (\r\n, \t\t 등) |
. | 뉴라인(\n) 을 제외한 모든 문자를 의미 | .{3} : 문자 3개 (F15, 0x0 등) |
정규식 패턴의 한 예로 "에러 {에러번호}"와 같은 형식을 띄는 부분을 발췌해 내는 예제를 살펴보자. 여기서 에러 패턴은 "에러" 라는 리터럴 문자열과 공백 하나, 그 뒤에 1개 이상의 숫자이다. 이를 표현하면 아래와 같다.
import re
text = "에러 1122 : 레퍼런스 오류\n 에러 1033: 아규먼트 오류"
regex = re.compile("에러\s\d+")
mc = regex.findall(text)
print(mc)
# 출력: ['에러 1122', '에러 1033']
위 예제는 첫번째 패턴 매칭값을 리턴하는 search() 메서드 대신 패턴에 매칭되는 모든 결과를 리턴하는 findall()을 사용하였다. findall()는 결과 문자열들의 리스트(list)를 리턴한다.
5. 정규식 그룹(Group)
정규 표현식에서 ( ) 괄호는 그룹을 의미한다. 예를 들어, 전화번호의 패턴을 \d{3}-\d{3}-\d{4} 와 같이 표현하였을 때, 지역번호 3자를 그룹1으로 하고 나머지 7자리를 그룹2로 분리하고 싶을 때, (\d{3})-(\d{3}-\d{4}) 와 같이 둥근 괄호로 묶어 두 그룹으로 분리할 수 있다.
이렇게 분리된 그룹들은 MatchObject의 group() 메서드에서 그룹 번호를 파라미터로 넣어 값을 가져올 수 있는데, 첫번째 그룹 지역번호는 group(1) 으로, 두번째 그룹은 group(2) 와 같이 사용한다. 그리고 전체 전화번호를 가져올 때는 group() 혹은 group(0) 을 사용한다.
import re
text = "문의사항이 있으면 032-232-3245 으로 연락주시기 바랍니다."
regex = re.compile(r'(\d{3})-(\d{3}-\d{4})')
matchobj = regex.search(text)
areaCode = matchobj.group(1)
num = matchobj.group(2)
fullNum = matchobj.group()
print(areaCode, num) # 032 232-3245
그룹을 위와 같이 숫자로 인덱싱하는 대신 그룹이름을 지정할 수도 있는데 이를 정규식에서 Named Capturing Group 이라 한다. 파이썬에서 Named Capturing Group을 사용하는 방법은 (?P<그룹명>정규식) 와 같이 정규식 표현 앞에 ?P<그룹명>을 쓰면 된다. 그리고 이후 MatchObject에서 group('그룹명') 을 호출하면 캡쳐된 그룹 값을 얻을 수 있다.
import re
text = "문의사항이 있으면 032-232-3245 으로 연락주시기 바랍니다."
regex = re.compile(r'(?P<area>\d{3})-(?P<num>\d{3}-\d{4})')
matchobj = regex.search(text)
areaCode = matchobj.group("area")
num = matchobj.group("num")
print(areaCode, num) # 032 232-3245
</num>
References
- https://docs.python.org/3/library/re.html?highlight=re#module-re
- 예제로 배우는 파이썬 프로그래밍
- https://wikidocs.net/1669
- https://wikidocs.net/4308#_5
4. Automated Testing and assert
5. Data Visualization
넘파이, matplotlib, random 모듈을 사용한 데이터 시각화