이번엔 드디어 저도 답을 모르고 시작하는 실전 데이터 분석에 대해서 다뤄보겠습니다. 주제는 이미 Kaggle에서 마감된 과제이지만, 그래도 해보고 싶었던 Sentimental Analysis에 대해서 해보려고 합니다. (자료출처 : Kaggle) Kaggle은 open competition으로 굉장히 유명하고 잘 정립된 사이트이니 data mining에 관심있으신 분들은 꾸준히 도전해보고 연습하시면 굉장히 많은 도움이 될 것입니다.

일단 기본적으로 Kaggle에서는 train과 test 데이터셋을 제공합니다. train 데이터셋을 가지고 머신러닝 모델을 만들고, 그 결과로 test 데이터셋에 대해서 예측을 하면 Kaggle에서 자동으로 점수를 매겨서 사람들과 비교할 수 있게 되는 것입니다.

Data 분석의 가장 기본이 되는 것은 저장된 data를 불러오는 것입니다. train.tsv를 보면 tab으로 각 column이 분리되어 있다는 것을 아실 수 있을 것입니다. 그럼 이런 파일을 python에서 읽어오려면 어떻게 해야할까요? 여기서 강력한 힘을 발휘하는 것이 pandas라는 라이브러리입니다. numpy와 함께 강력한 data analysis platform으로써 활약합니다. pandasread_csv함수를 통해서 sep를 tab으로 설정하여 데이터를 불러오면 됩니다 :)

>>> import pandas as pd
>>> train = pd.read_csv('train.tsv', sep='\t')
>>> train[:3] #잘 읽어왔는지 확인할 수 있습니다.
  PhraseId SentenceId Phrase Sentiment
0 1 1 A series of escapades demonstrating the adage … 1
1 2 1 A series of escapades demonstrating the adage … 2
2 3 1 A series 2

기본적으로 분석하기 전에 데이터가 어떻게 생겼는지 살펴보는 것이 매우 중요합니다!! 가만히 보니 SentenceId항목이 중복되어 있는 것을 확인하실 수 있죠? 여러 행을 보면 아시겠지만, 동일한 Phrase가 중복되기도 하고, 왜 이렇게 만들어졌는지는 잘 모르겠네요^^;; 어쨌든 저는 substring이 아닌 온전한 문장을 원하기 때문에, 가장 처음으로 등장하는 SentenceId에 해당하는 것만 뽑아서 새로운 dataset을 만들도록 하겠습니다. 그러면 먼저 특정 column만 추출하려면 어떻게 해야할까요?

>>> train['SentenceId'][:3]
0    1
1    1
2    1
Name: SentenceId, dtype: int64

이런식으로 string으로 column name을 입력해주면 해당 column만 추출되므로 우리가 아는 indexing으로 해주면 됩니다. 그럼 이제 본격적으로 중복되는 SentenceId를 제거해보는 코드를 짜보겠습니다.

>>> import numpy as np
>>> nrow = len(train) #train이 몇 행으로 구성되어있는지 파악합니다.

#그리고 train을 slicing할 boolean array를 동일한 크기만큼 만들어줍니다.
>>> indVec = np.empty(nrow, dtype='bool')
>>> print indVec
[ True False False ..., False False False]

##본격적으로 loop를 돌면서 처음 등장하는 SentenceId에 한해서만 저장하고 나머진 제거합니다.
>>> sentenceId = 0
>>> for i in range(nrow):
...     if train['SentenceId'][i] != sentenceId:
...         sentenceId = train['SentenceId'][i]
...         indVec[i] = True
...     else:
...         indVec[i] = False

>>> train_filtered = train[indVec]
>>> train_filtered[:3]
  PhraseId SentenceId Phrase Sentiment
0 1 1 A series of escapades demonstrating the adage … 1
63 64 2 This quiet , introspective and entertaining in… 4
81 82 3 Even fans of Ismail Merchant ‘s work , I suspe… 1

와우! 정리가 아주 깔끔하게 잘 되었다는 것을 확인할 수 있죠? :) 그럼 이제 우리가 원하는 ‘Phrase’를 처리해야하는데요, 문제가 조금 있습니다. 맨 앞을 보시면 0, 1, 2, … 이런식으로 가는 것이 아니라, 0, 63, 81, … 이런식으로 우리가 제거했다는 것이 티가 나죠? 이렇게 되면 연속된 정수로 indexing을 할 수가 없게 됩니다. 따라서 이것을 다시 0, 1, 2, … 이런식으로 바꿔줘야합니다.

>>> train_filtered.index
Int64Index([0, 63, 81, 116, 156, 166, 198, 213, 247, 259, 271, 306, 352, 363, 407, 423, 458, 473, 516, 536, 551, 563, 619, 647, 686, 702, 725, 741, 786, 810, 839, 848, 881, 916, 944, 970, 1002, 1023, 1050, 1087, 1099, 1134, 1164, 1198, 1215, 1224, 1257, 1280, 1304, 1332, 1368, 1400, 1445, 1479, 1505, 1522, 1530, 1538, 1568, 1608, 1627, 1658, 1686, 1715, 1734, 1754, 1798, 1839, 1845, 1882, 1917, 1937, 1965, 1972, 1983, 2005, 2012, 2033, 2070, 2086, 2101, 2141, 2194, 2217, 2234, 2243, 2273, 2296, 2314, 2337, 2368, 2393, 2413, 2452, 2472, 2503, 2519, 2555, 2580, 2597, ...], dtype='int64')

위와 같이 index라는 attribute에 우리가 바꾸고자 하는 것들이 저장되어 있습니다. 따라서 이것을 다시 우리가 원하는 대로 setting해 주면 되겠죠?

>>> train_filtered.index = range(len(train_filtered))
>>> print train_filtered.index #이렇게 해야만 나중에 for문으로 access가 가능합니다.
Int64Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...], dtype='int64')

>>> print len(train_filtered)
8529 #기존의 156060행에서 엄청나게 줄어들었음을 확인하실 수 있죠?

자, 이제 모든 것이 준비되었으니, 본격적으로 한 phrase씩 다뤄보겠습니다. 각 phrase는 string 형태이기 때문에, split함수를 통해서 공백을 기준으로 한 단어씩 나눠서 list로 만듭니다.

>>> for i in range(3):
...    phrase = train_filtered['Phrase'][i]
...    words = phrase.split()
...    print words
['A', 'series', 'of', 'escapades', 'demonstrating', 'the', 'adage', 'that', 'what', 'is', 'good', 'for', 'the', 'goose', 'is', 'also', 'good', 'for', 'the', 'gander', ',', 'some', 'of', 'which', 'occasionally', 'amuses', 'but', 'none', 'of', 'which', 'amounts', 'to', 'much', 'of', 'a', 'story', '.']
['This', 'quiet', ',', 'introspective', 'and', 'entertaining', 'independent', 'is', 'worth', 'seeking', '.']
['Even', 'fans', 'of', 'Ismail', 'Merchant', "'s", 'work', ',', 'I', 'suspect', ',', 'would', 'have', 'a', 'hard', 'time', 'sitting', 'through', 'this', 'one', '.']

다음으로 이렇게 분리된 list를 가지고 각 단어가 몇번 출현하는지 나타내는 dictionary 자료구조로 변경해 보겠습니다. 여기서 사용할 자료구조는 dictionary의 변형된 형태인 defaultdict인데, key생성과 동시에 default값을 가지고 있게 되어 Key가 없는 상태여도 Key Error가 나지 않는다는 특징이 있습니다.

>>> from collections import defaultdict
>>> wordList = defaultdict(int) #기본값으로 0을 가지게 됩니다.
>>> for i in range(3):
...    phrase = train_filtered['Phrase'][i]
...    words = phrase.split()
...    for w in words:
...        wordList[w] += 1
>>> print wordList
defaultdict(<type 'int'>, {'and': 1, 'Even': 1, 'would': 1, 'series': 1, 'is': 3, 'hard': 1, 'some': 1, 'one': 1, 'goose': 1, 'through': 1, 'have': 1, 'story': 1, 'Merchant': 1, 'what': 1, 'for': 2, 'to': 1, 'amounts': 1, 'much': 1, 'seeking': 1, ',': 4, '.': 3, 'also': 1, 'fans': 1, 'occasionally': 1, 'suspect': 1, 'which': 2, 'worth': 1, 'A': 1, 'independent': 1, 'good': 2, "'s": 1, 'that': 1, 'This': 1, 'gander': 1, 'but': 1, 'adage': 1, 'demonstrating': 1, 'sitting': 1, 'entertaining': 1, 'a': 2, 'introspective': 1, 'none': 1, 'I': 1, 'escapades': 1, 'this': 1, 'of': 5, 'work': 1, 'quiet': 1, 'Ismail': 1, 'time': 1, 'amuses': 1, 'the': 3})

이런식으로 처리된다는 것을 아실 수 있겠죠? 그럼 본격적으로 모든 데이터에 대해서 wordList를 만들겠습니다.

>>> wordList = defaultdict(int)
>>> nrow = len(train_filtered)
>>> for i in range(nrow):
...    phrase = train_filtered['Phrase'][i]
...    words = phrase.split()
...    for w in words:
...        wordList[w] += 1

>>> len(wordList) 
18132 #distinct한 단어의 수가 18132개입니다.

그럼 이제 등장 횟수가 많은 단어들부터 나오도록 정렬해봅시다.

>>> sorted_wordList = sorted(wordList.items(), key=lambda x: x[1], reverse=True)
>>> print sorted_wordList[:10]
[('.', 7935), (',', 7066), ('the', 5977), ('and', 4384), ('a', 4358), ('of', 4330), ('to', 2970), ("'s", 2517), ('is', 2513), ('that', 1898)]

일일이 하는 방법도 있지만, 조금 더 편리하게 하려면 DataFrame의 내장함수를 이용하실 수도 있습니다.

>>> split_strings = train_filtered['Phrase'].str.split() # 이렇게 간단하게 split된 list를 얻을 수도 있습니다.
>>> split_strings[:3]
0    [A, series, of, escapades, demonstrating, the,...
1    [This, quiet, ,, introspective, and, entertain...
2    [Even, fans, of, Ismail, Merchant, 's, work, ,...
Name: Phrase, dtype: object

이럴 땐 다음과 같이 sort할 수 있겠죠?

>>> wordList = defaultdict(int)
>>> for i in range(len(split_strings)):
...    for w in split_strings[i]:
...        wordList[w] += 1

>>> sorted_wordList = sorted(wordList.items(), key=lambda x: x[1], reverse=True)
>>> print sorted_wordList[:10]
[('.', 7935), (',', 7066), ('the', 5977), ('and', 4384), ('a', 4358), ('of', 4330), ('to', 2970), ("'s", 2517), ('is', 2513), ('that', 1898)]

참 이것저것 많은 것을 한 것 같네요. 기본적으로 데이터를 어떻게 불러올 것인지와, pandas dataframe의 기본적인 구조?와 data cleaning에 대해서 다뤄볼 수 있었던 좋은 시간이었네요 :)