이번엔 드디어 저도 답을 모르고 시작하는 실전 데이터 분석에 대해서 다뤄보겠습니다. 주제는 이미 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으로써 활약합니다. pandas
의 read_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에 대해서 다뤄볼 수 있었던 좋은 시간이었네요 :)