2. 리스트와 사전#
아래 그림은 여러 개의 값을 하나의 값으로 묶어 처리할 수 있도록 도와주는 네 개의 내장 자료 구조built-in data structure를 표현한다. 내장built-in은 파이썬이 기본으로 제공한다는 의미다. 반면에 자료 구조data structure는 정수, 부동소수점, 문자열과는 다르게 여러 개의 값으로 구성된 보다 복잡한 값을 표현하는 객체를 가리킨다.

데이터 사이언스에서 가장 많이 활용되는 넘파이 어레이 객체와 판다스 데이터프레임 객체의 활용법 이해에 도움이 되는 리스트와 사전을 간략하게 소개한다.
2.1. 리스트#
리스트는 여러 개의 값들을 순차적으로 모아놓은 객체이다.
아래 코드에서
int_list는 정수로 구성된 리스트를,
float_list는 부동소수점으로 구성된 리스트를,
str_list는 문자열로 구성된 리스트를
가리킨다.
int_list = [2, 3, 7, 11]
float_list = [3.14, 2.17, 7.0, -0.856, 20.8]
str_list = ['foo', 'bar', 'baz']
반면에 아래 코드의 변수 comp_list처럼,
하나의 리스트에 정수, 소수, 문자열, 리스트 등 다양한 유형의 값들이 함께 담길 수 있다.
리스트를 항목으로 갖는 리스트를 중첩 리스트라 부른다.
list_ragged 변수가 가리키는 리스트처럼
항목으로 사용된 리스트들의 길이가 서로 달라도 무방하다.
comp_list = [2, 3.14, 'foo', int_list]
list_ragged = [[1, 2, 3, 4], [5, 6, 7], [8, 9]]
리스트의 자료형은 포함된 항목의 자료형과 무관하게 list로 지정된다.
type(int_list)
list
type(comp_list)
list
type(list_ragged)
list
리스트의 길이는 포함된 항목의 개수를 가리킨다. 중첩 리스트의 길이는 항목으로 사용된 리스트의 길이와 무관하다.
len(float_list)
5
len(comp_list)
4
len(list_ragged)
3
2.1.1. 리스트 인덱싱#
리스트에 포함된 항목의 위치를 가리키는 인덱스index를 이용하여 항목을 확인하거나 다른 값으로 대체할 수 있다.
int_list[0]
2
int_list[0] = 1
int_list
[1, 3, 7, 11]
comp_list[3]
[1, 3, 7, 11]
comp_list[1] = 'peekaboo'
comp_list
[2, 'peekaboo', 'foo', [1, 3, 7, 11]]
중첩 리스트에 대해서는 인덱싱을 반복적으로 적용할 수 있다.
list_ragged[2]
[8, 9]
list_ragged[2][1]
9
list_ragged[2] = [10,11,12,13]
list_ragged[2][3] = 14
list_ragged
[[1, 2, 3, 4], [5, 6, 7], [10, 11, 12, 14]]
-1, -2, -3 등 음수 인덱스는 리스트 오른쪽에서부터 위치이다. -1번 인덱스는 리스트의 오른쪽 끝 항목, -2번 인덱스는 오른쪽 끝에서 두번째 항목과 같은 식이며,이는 중첩 리스트에서도 동일하게 적용된다.
list_ragged[-2]
[5, 6, 7]
list_ragged[-3][-2] = 8
list_ragged
[[1, 2, 8, 4], [5, 6, 7], [10, 11, 12, 14]]
2.1.2. 리스트 슬라이싱#
슬라이싱은 두 개의 인덱스로 지정된 구간에 포함된 항목들을 추출해 별도의 리스트를 생성한다. 보폭을 통해 몇 걸음씩 건너뛸지도 설정할 수 있다.
시작 인덱스: 슬라이싱 구간 시작 인덱스. 생략되면 기본값인 0을 적용.
끝 인덱스: 슬라이싱 구간 끝 인덱스. 실제 구간은 이 인덱스의 이전 항목까지임. 생략되면 기본값인 리스트의 길이(즉, 오른쪽 끝 인덱스+1)를 적용.
보폭: 구간 시작부터 몇 개씩 건너뛰며 항목을 확인할 것인지 지정. 생략되면 기본값인 1을 적용.
구분 |
기능 |
|---|---|
시작 인덱스 |
슬라이싱 구간 시작 인덱스. 생략되면 기본값인 0을 적용. |
끝 인덱스 |
슬라이싱 구간 끝 인덱스. 실제 구간은 이 인덱스의 이전 항목까지임. 생략되면 기본값인 리스트의 길이(즉, 오른쪽 끝 인덱스+1)를 적용. |
보폭 |
구간 시작부터 몇 개씩 건너뛰며 항목을 확인할 것인지 지정. 생략되면 기본값인 1을 적용. |
아래 코드는 1번부터 4번 인덱스의 항목들로 이루어진 별도의 리스트 sub_seq를 생성한다.
이어지는 그림은 seq와 sub_seq 두 변수 각각이 가리키는 리스트가 메모리에 저장된 상태를 보여준다.
seq = [7, 2, 3, 7, 5, 6, 0, 1]
sub_seq = seq[1:5]
sub_seq
[2, 3, 7, 5]

슬라이싱 명령문의 시작과 끝 인덱스, 보폭은 모두 필요에 따라 생략될 수 있다. 시작 인덱스가 생략되면 리스트의 처음(인덱스 0)을, 끝 인덱스가 생략되면 리스트의 길이(즉, 오른쪽 끝 인덱스+1)를, 보폭이 생략되면 1을 적용한다. 음수의 보폭을 사용하면 역순으로 항목들이 추출되는데, 첫째 인자는 원하는 구간의 오른쪽 끝 인덱스, 둘째 인자는 원하는 구간의 왼쪽끝 인덱스-1로 설정해야 한다.
리스트 슬라이싱 표현식 |
실제 의미 |
출력 결과 |
|---|---|---|
|
|
[2,3,7,5] |
|
|
[7,2,3,7,5] |
|
|
[7,5,6,0,1] |
|
|
[5, 6, 0] |
|
|
[3, 7, 5, 6] |
|
|
[7,3,5,0] |
|
|
[1,6,7,2] |
|
|
[1, 0, 6, 5, 7, 3, 2, 7] |
2.1.3. 리스트 연산#
두 개의 리스트를 이어붙이거나 하나의 리스트를 복제해서 이어붙이는 기능을 지원한다. 아래 코드는 두 개의 리스트를 이어붙여서 새로운 리스트를 생성하는 방법을 보여준다.
int_list = [2, 3, 7, 11]
str_list = ['foo', 'bar', 'baz']
int_list + str_list
[2, 3, 7, 11, 'foo', 'bar', 'baz']
아래 코드는 하나의 리스트를 지정된 정수만큼 복제해서 이어붙인다.
int_list * 3
[2, 3, 7, 11, 2, 3, 7, 11, 2, 3, 7, 11]
2 * int_list
[2, 3, 7, 11, 2, 3, 7, 11]
2.2. 사전#
현대 프로그래밍 언어 분야, 특히 데이터 분석 분야에서 가장 중요하게 사용되는 자료형 중의 하나가 사전 dict 이다.
언어에 따라 표현과 사용법에 조금씩 차이가 있지만 연관 배열associative array 또는 해시 테이블hash table로 불린다.
사전 자료형은 키key와 값value의 쌍으로 구성된 항목들의 모음이며, 아래 형식으로 키와 값의 관계를 지정한다.
키 : 값
사전 객체 생성
사전 객체를 생성하기 위해서는 중괄호 또는 dict() 함수가 사용된다.
예를 들어 비어 있는 사전 객체는 다음과 같이 생성한다.
empty_dict = {}
empty_dict
{}
첫번째 코드는 중괄호 내부에 키:값 형식의 항목들을 입력하여 사전 객체를 생성하는 방법이고, 두번째 코드는 dict() 함수에 튜플의 리스트를 입력하여 사전을 생성하는 방법이다.
dict_ab = {'a' : 'some value', 'b' : [1, 2, 3, 4]}
dict_ab
{'a': 'some value', 'b': [1, 2, 3, 4]}
dict([(1, 'a'), (2, 'b')])
{1: 'a', 2: 'b'}
dict([[1, 'a'], [2, 'b']])
{1: 'a', 2: 'b'}
keys() 메서드와 values() 매서드
key() 메서드는 사전의 키만 모아 별도의 리스트를 생성하고, value() 메서드는 값만을 모은 리스트를 만든다.
list(dict_ab.keys())
['a', 'b']
list(dict_ab.values())
['some value', [1, 2, 3, 4]]
사전의 항목 추가 및 업데이트
사전 dict_ab에 새로운 항목 7 : 'an integer' 를 추가하려면 아래와 같이 진행해야 한다.
dict_ab[7] = 'an integer'
dict_ab
{'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer'}
아래 코드는 'language' : 'python' 을 추가한다.
dict_ab['language'] = 'python'
dict_ab
{'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer', 'language': 'python'}
항목의 키를 이용 기존의 항목 키-값 에서 값을 변경할 수 있다.
예를 들어, 아래 코드는 키 'language'에 대응하는 값을 'python' 에서 'python3'로 업데이트한다.
dict_ab['language'] = 'python3'
dict_ab
{'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer', 'language': 'python3'}
사전의 항목 확인
특정 키가 사전에 사용되었는지 여부를 확인할 때 in 연산자를 활용한다.
'b' in dict_ab
True
특정 키와 연관된 값을 확인하려면 인덱싱 방식처럼 키를 사용하면 된다.
dict_ab['b']
[1, 2, 3, 4]
2.3. 리스트, 사전과 함께 사용하기 유용한 함수#
range() 함수
range() 함수는 규칙성을 가진 정수들의 모음을 반환한다.
첫째 인자는 원하는 정수 구간의 시작값을, 둘째 인자는 원하는 구간의 끝값보다 1만큼 큰 값으로 입력해야 한다. 반환값의 자료형은 range 객체이다.
예를 들어, 0부터 9까지의 정수들로 이루어진 range 객체를 다음과 같이 생성한다.
range(10)
range(0, 10)
type(range(10))
range
반면에 아래 코드는 1부터 9까지의 정수를 담은 range 객체를 생성한다.
range(1, 10)
range(1, 10)
range(0, 10)은 range(10)과 동일한데 이유는 첫째 인자가 0일 때 생략 가능하기 때문이다.
range(0, 10)
range(0, 10)
list 객체와 달리 range 객체의 내부는 들여다볼 수 없다.
print(range(0,10))
range(0, 10)
리스트로 형변환하면 그제서야 range 객체를 리스트 객체로 변환해서 항목들을 보여준다.
이처럼 range() 함수를 이용하여 range 객체를 생성한 다음, 리스트로 형변환하여 활용하는 방법이 매우 빈번하게 사용된다.
list(range(0,10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
range() 함수에서도 보폭을 사용할 수 있다.
예를 들어, 0에서 19까지의 정수중에서 짝수만으로 이루어진 range 객체는 다음과 같이 보폭 2를 셋째 인자로 지정하여 생성한다.
list(range(0, 20, 2))
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
리스트 슬라이싱에서처럼 음수의 보폭을 이용해 크기 역순으로 정수를 담는 range 객체를 만들 수도 있다.
첫째 인자는 원하는 구간의 오른쪽 끝 정수, 둘째 인자는 원하는 구간의 왼쪽끝 정수보다 1만큼 작은 값으로 설정하면 된다.
list(range(5, 0, -2))
[5, 3, 1]
range 객체가 리스트보다 훨씬 적은 메모리를 사용하기 때문에, for 반복문의 인덱스 집합으로 자주 활용된다.
예를 들어, 아래 두 코드는 0부터 99,999 까지의 정수 중에서 3 또는 5의 배수를 모두 더하는 경우와, 리스트의 길이 정보를 이용해 활용되는 예를 보여준다.
sum = 0
for i in range(100_000):
# %는 나머지 연산자
if i % 3 == 0 or i % 5 == 0:
sum += i
print(sum)
2333316668
seq = [1, 2, 3, 4]
for i in range(len(seq)):
val = seq[i]
print(val)
1
2
3
4
enumerate() 함수
리스트의 항목과 해당 항목의 인덱스 정보를 함께 활용해야 할 때, enumerate() 함수가 매우 유용하다.
enumerate() 함수는 리스트를 받아서
리스트의 항목과 인덱스의 정보를 제공하는 객체를 지정한다.
아래 코드는 for 반복문을 이용하여 리스트의 내용을 확인하는 예로서, 짝수 인덱스에 해당하는 리스트 항목만 출력한다.
some_list = ['foo', 'bar', 'baz', 'pyt', 'thon']
for i, v in enumerate(some_list):
if i % 2 == 0:
print(v)
foo
baz
thon
반면에 아래 코드는 리스트의 항목을 키(key)로, 인덱스는 값(value)으로 갖는 항목들로 이루어진 사전 객체를 생성한다.
mapping = {}
for i, v in enumerate(some_list):
mapping[v] = i
mapping
{'foo': 0, 'bar': 1, 'baz': 2, 'pyt': 3, 'thon': 4}
zip() 함수
zip() 함수는 문자열, 튜플, 리스트 여러 개의 항목을 순서대로 짝지어서 튜플의 리스트 형식의 객체를 생성한다. ‘range()’ 객체에서처럼, 리스트로 형변환해야만 내용이 확인된다.
list(zip("abc", "efg"))
[('a', 'e'), ('b', 'f'), ('c', 'g')]
자료형이 달라도 되며, 각 자료형의 길이가 다르면 짧은 길이에 맞춰서 짝을 짓는다.
list(zip("abc", [1, 2]))
[('a', 1), ('b', 2)]
seq1 = ['foo', 'bar', 'baz']
seq2 = ['one', 'two', 'three']
zipped = zip(seq1, seq2)
list(zipped)
[('foo', 'one'), ('bar', 'two'), ('baz', 'three')]
앞의 함수들처럼 for 반복문에 자주 활용된다.
아래 코드는 두 개의 리스트의 항목을 짝을 지은 후 인덱스와 함께 출력해준다.
for i, (a, b) in enumerate(zip(seq1, seq2)):
print(f"{i}: {a}, {2}")
0: foo, 2
1: bar, 2
2: baz, 2
사전 객체 생성에도 자주 활용된다.
mapping = dict(zip(range(5), reversed(range(5))))
mapping
{0: 4, 1: 3, 2: 2, 3: 1, 4: 0}
f-문자열 f-string
f-문자열은 Python 3.6부터 도입된 기능으로 문자열 포맷팅을 간결하고 읽기 쉽게 만들어준다. 문자열 앞에 f를 붙이면 문자열 내부에 값이 확정되지 않는 변수나 {}로 감싼 표현식을 넣을 수 있다.
주요 사용법은 다음과 같다.
기능 |
설명 |
예시 코드 |
출력 결과 |
|---|---|---|---|
변수 삽입 |
문자열 내부에 변수 값을 직접 삽입 |
|
안녕, Alice! |
표현식 삽입 |
문자열 내부에 간단한 파이썬 표현식 삽입 |
|
10 + 20 = 30 |
소수점 자릿수 제한 |
부동소수점 숫자의 소수점 이하 자릿수 지정 |
|
3.14 |
지수 표기법 |
숫자를 과학적(지수) 표기법으로 표시 |
|
1.2e+06 |
천 단위 구분 기호 |
정수 또는 부동소수점 숫자에 천 단위 구분 기호(쉼표) 추가 |
|
1,234,567 |
백분율 표시 |
숫자를 백분율로 표시 |
|
75.20% |