11 . 튜플(Tuples)

파이썬 튜플은 다양한 자료형을 갖는 리스트에 대응된다. 튜플이 갖는 불변성이 R에서 데이터 분석 등의 작업에 꼭 필요한 기능은 아니라서 불변성을 활용한 튜플 개념이 많이 사용되지는 않고 있다.

하지만, 다양한 자료형을 갖는 리스트는 데이터프레임을 확장한 개념으로 고급 데이터 분석과 처리에 필히 이해를 가져야하는 내용이다.

추후 다양한 자료형을 갖는 리스트는 별도 장을 따로 만들어 풀어나갈 예정이다.

11.1 튜플은 불변이다

다양한 자료형을 갖는 리스트가 튜플(tuple)이다. 튜플(tuple)[^about-tuple]은 리스트와 마찬가지로 순서(sequence) 값이다. 튜플에 저장된 값은 임의 자료형(type)이 될 수 있고, 정수로 색인 된다. 일반적으로 알려진 튜플은 불변(immutable하다는 것이다. 튜플은 비교 가능(comparable)하고 해쉬형(hashable)이다. 따라서, 리스트 값을 정렬할 수 있고, 키 값으로 튜플을 사용할 수 있다.

[^about-tuple] : 재미난 사실: 단어 ‘’튜플(tuple)’’은 가변 길이 (한배, 두배, 세배, 네배, 다섯배, 여섯배, 일곱배 등) 숫자열에 붙여진 이름에서 유래한다

구문론적으로, 튜플은 콤마로 구분되는 서로 다른 자료형을 갖는 리스트 값이다.

t <- list('a', 'b', 'c', 'd', 'e')

튜플(tuple)이 생성자 이름이기 list이기 때문에 변수명으로 리스트(list) 사용을 피해야 한다.

대부분의 리스트 연산자는 튜플에서도 사용 가능하다. 꺾쇠 연산자가 요소를 색인한다.

t[1]
## [[1]]
## [1] "a"

그리고, 슬라이스 연산자(slice operator)는 요소 범위를 선택한다.

t[2:3]
## [[1]]
## [1] "b"
## 
## [[2]]
## [1] "c"

하지만, 파이썬 튜플 요소 중 하나를 변경하고 하면 오류가 발생하지만, R에서 리스트는 다양한 자료형을 담을 수 있고 변경도 가능하다.

t[1] <- 'A'
t
## [[1]]
## [1] "A"
## 
## [[2]]
## [1] "b"
## 
## [[3]]
## [1] "c"
## 
## [[4]]
## [1] "d"
## 
## [[5]]
## [1] "e"

11.2 가장 빈도수 높은 단어

로미오와 쥴리엣 2장 2막 텍스트 파일(romeo-full.txt)로 다시 돌아와서, 텍스트에서 가장 빈도수가 높은 단어를 10개를 출력하는 프로그램을 작성한다.

library(tidyverse)

romeo_text <- readr::read_lines("data/romeo-full.txt")

romeo_split <- stringr::str_split(romeo_text, " ") %>%
  unlist() %>%
  stringr::str_to_lower() %>%
  stringr::str_replace_all(., pattern = "[[:punct:]]",
                           replacement = "")

romeo_split <- romeo_split[romeo_split != ""]

romeo_full_freq <- romeo_split %>% unlist() %>%
  table() %>%
  as_tibble() %>% 
  set_names(c("key", "value")) %>% 
  arrange(desc(value)) %>% 
  slice_head(n=10)

# romeo_full_freq

파일을 읽고 각 단어를 문서의 단어 빈도수는 전처리 로직으로 구두점을 제거하고 모두 소문자로 변환한 후에 table() 함수로 단어별 빈도수를 계산하고 데이터프레임으로 변환 후 고빈도 단어 10개를 추출한다.

이제 단어 빈도 분석을 위해서 작성한 프로그램의 마지막 출력결과는 원하는 바를 완수한 것처럼 보인다.

## # A tibble: 10 × 2
##   key    value
##   <chr>  <int>
## 1 i         61
## 2 and       42
## 3 romeo     40
## 4 the       34
## 5 to        34
## 6 juliet    32
## # … with 4 more rows

11.3 순열: 문자열, 리스트, 튜플

여기서 리스트 튜플에 초점을 맞추었지만, 이장의 거의 모든 예제가 또한 리스트의 리스트, 튜플의 튜플, 리스트 튜플에도 동작한다. 가능한 모든 조합을 열거하는 것을 피하기 위해서, 순열의 순열(sequences of sequences)에 대해서 논의하는 것이 때로는 쉽다.

대부분의 문맥에서 다른 종류의 순열(문자열, 리스트, 튜플)은 상호 호환해서 사용될 수 있다. 그런데 왜 그리고 어떻게 다른 것보다 이것을 선택해야 될까?

서로 다른 자료형이 갖는 특징을 잘 파악하고 주어진 문제를 가장 쉽게 풀수 있는 방향의 자료형을 선택한다.

11.4 디버깅

리스트, 딕셔너리, 튜플은 자료 구조(data structures)로 일반적으로 알려져 있다. 이번장에서 리스트 튜플, 키로 튜플, 값으로 리스트를 담고 있는 딕셔너리 같은 복합 자료 구조를 보기 시작했다. 복합 자료 구조는 유용하지만, 저자가 작명한 형태 오류(shape errors)라고 불리는 오류에 노출되어 있다. 즉, 자료 구조가 잘못된 자료형(type), 크기, 구성일 경우 오류가 발생한다. 혹은 코드를 작성하고, 자료의 모양이 생각나지 않는 경우도 오류의 원인이 된다.

예를 들어, 정수 하나를 가진 리스트를 기대하고, (리스트가 아닌) 일반 정수를 넘긴다면, 작동하지 않는다.

프로그램을 디버깅할 때, 정말 어려운 버그를 잡으려고 작업을 한다면, 다음 네가지를 시도할 수 있다.

  1. 코드 읽기(reading): 코드를 면밀히 조사하고, 스스로에게 다시 읽어 주고, 코드가 자신이 작성한 의도를 담고 있는지 점검하라.

  2. 실행(running): 변경해서 다른 버젼을 실행해서 실험하라. 종종, 프로그램이 적절한 곳에 적절한 것을 보여준다면, 문제가 명확하다. 발판(scaffolding)을 만들기 위해서 때때로 시간을 들일 필요도 있다.

  3. 반추(ruminating):생각의 시간을 갖자. 어떤 종류의 오류인가: 구문, 실행, 의미론(semantic). 오류 메시지로부터 혹은 프로그램 출력결과로부터 무슨 정보를 얻을 수 있는가? 어떤 종류 오류가 지금 보고 있는 문제를 만들었을까? 문제가 나타나기 전에, 마지막으로 변경한 것은 무엇인가?

  4. 퇴각(retreating): 어느 시점에선가, 최선은 물러서서, 최근의 변경을 다시 원복하는 것이다. 잘 동작하고 이해하는 프로그램으로 다시 돌아가서, 다시 프로그램을 작성한다.

초보 프로그래머는 종종 이들 활동 중 하나에 사로잡혀 다른 것을 잊곤 한다. 활동 각각은 고유한 실패 방식과 함께 온다.

예를 들어, 프로그램을 정독하는 것은 문제가 인쇄상의 오류에 있다면 도움이 되지만, 문제가 개념상 오해에 뿌리를 두고 있다면 그다지 도움이 되지 못한다. 만약 작성한 프로그램을 이해하지 못한다면, 100번 읽을 수는 있지만, 오류를 발견할 수는 없다. 왜냐하면, 오류는 여러분 머리에 있기 때문입니다.

만약 작고 간단한 테스트를 진행한다면, 실험을 수행하는 것이 도움이 될 수 있다. 하지만, 코드를 읽지 않거나, 생각없이 실험을 수행한다면, 프로그램이 작동될 때까지 무작위 변경하여 개발하는 “랜덤 워크 프로그램(random walk programming)” 패턴에 빠질 수 있다. 말할 필요없이 랜덤 워크 프로그래밍은 시간이 오래 걸린다.

생각할 시간을 가져야 한다. 디버깅은 실험 과학 같은 것이다. 문제가 무엇인지에 대한 최소한 한 가지 가설을 가져야 한다. 만약 두개 혹은 그 이상의 가능성이 있다면, 이러한 가능성 중에서 하나라도 줄일 수 있는 테스트를 생각해야 한다.

휴식 시간을 가지는 것은 생각하는데 도움이 된다. 대화를 하는 것도 도움이 된다. 문제를 다른 사람 혹은 자신에게도 설명할 수 있다면, 질문을 마치기도 전에 답을 종종 발견할 수 있다.

하지만, 오류가 너무 많고 수정하려는 코드가 매우 크고, 복잡하다면 최고의 디버깅 기술도 무용지물이다. 가끔, 최선의 선택은 퇴각하는 것이다. 작동하고 이해하는 곳까지 후퇴해서 프로그램을 간략화하라.

초보 프로그래머는 종종 퇴각하기를 꺼려한다. 왜냐하면, 설사 잘못되었지만, 한줄 코드를 지울 수 없기 때문이다. 삭제하지 않는 것이 기분을 좋게 한다면, 다시 작성하기 전에 프로그램을 다른 파일에 복사하라. 그리고 나서, 한번에 조금씩 붙여넣어라.

정말 어려운 버그(hard bug)를 발견하고 고치는 것은 코드 읽기, 실행, 반추, 때때로 퇴각을 요구한다. 만약 이들 활동 중 하나도 먹히지 않는다면, 다른 것들을 시도해 보세요.

11.5 용어정의

  • 비교가능한(comparable):동일한 자료형의 다른 값과 비교하여 큰지, 작은지, 혹은 같은지를 확인하기 위해서 확인할 수 있는 자료형(type). 비교가능한(comparable) 자료형은 리스트에 넣어서 정렬할 수 있다.
  • 자료 구조(data structure):연관된 값의 집합, 종종 리스트, 딕셔너리, 튜플 등으로 조직화된다.
  • DSU: “decorate-sort-undecorate,”의 약어로 리스트 튜플을 생성, 정렬, 결과 일부 추출을 포함하는 패턴.
  • 모음(gather): 가변-길이 인자 튜플을 조합하는 연산.
  • 해쉬형(hashable): 해쉬 함수를 가진 자료형(type). 정수, 소수점, 문자열 같은 불변형은 해쉬형이다. 리스트나 딕셔너리 처럼 변경가능한 형은 해쉬형이 아니다.
  • 스캐터(scatter): 순서(sequence)를 리스트 인자로 다루는 연산.
  • (자료 구조의) 형태: 자료 구조의 자료형(type), 크기, 구성을 요약.
  • 싱글톤(singleton): 단일 요소를 가진 리스트 (혹은 다른 순서(sequence)).
  • 튜플(tuple): 불변 요소들의 순서 (sequence).
  • 튜플 대입(tuple assignment): 오른편 순서(sequence)와 왼편 튜플 변수를 대입. 오른편이 평가되고나서 각 요소들은 왼편의 변수에 대입된다.

11.6 연습문제

  1. 앞서 작성한 프로그램을 다음과 같이 수정하세요. “From”라인을 읽고 파싱하여 라인에서 주소를 뽑아내세요. 딕셔너리를 사용하여 각 사람으로부터 메시지 숫자를 계수(count)한다.

  2. 모든 데이터를 읽은 후에 가장 많은 커밋(commit)을 한 사람을 출력하세요. 딕셔너리로부터 리스트 (count, email) 튜플을 생성하고 역순으로 리스트를 정렬한 후에 가장 많은 커밋을 한 사람을 출력하세요.

    Sample Line:
    From stephen.marquard@uct.ac.za Sat Jan  5 09:14:16 2008

    Enter a file name: mbox-short.txt
    cwen@iupui.edu 5

    Enter a file name: mbox.txt
    zqian@umich.edu 195
  1. 이번 프로그램은 각 메시지에 대한 하루 중 시간의 분포를 계수(count)한다. “From” 라인으로부터 시간 문자열을 찾고 콜론(:) 문자를 사용하여 문자열을 쪼개서 시간을 추출합니다. 각 시간별로 계수(count)를 누적하고 아래에 보여지듯이 시간 단위로 정렬하여 한 라인에 한시간씩 계수(count)를 출력합니다.
    Sample Execution:
    Rscript timeofday.R
    Enter a file name: mbox-short.txt
    04 3
    06 1
    07 1
    09 2
    10 3
    11 6
    14 1
    15 2
    16 4
    17 2
    18 1
    19 1
  1. 파일을 읽고, 빈도(frequencey)에 따라 내림차순으로 문자(letters)를 출력하는 프로그램을 작성하세요. 작성한 프로그램은 모든 입력을 소문자로 변환하고 a-z 문자만 계수(count)한다. 공백, 숫자, 문장기호 a-z를 제외한 다른 어떤 것도 계수하지 않습니다. 다른 언어로 구성된 텍스트 샘플을 구해서 언어마다 문자 빈도가 어떻게 변하는지 살펴보세요. 결과를 wikipedia.org/wiki/Letter_frequencies 표와 비교하세요.