Category Archives: kaggle

Disaster Tweet 코드 분석 1편 – 데이터 탐색

이번 포스트에서는 Kaggle의 Real or Not NLP with Disaster Tweet 캐글 컴퍼티션에서 Most Votes를 받은 주피터 노트북 코드를 정리해보려고 합니다. 참고한 코드는 현재 포스트에 표시된 하이퍼링크를 클릭하시면, 보실 수 있습니다. 코드는 그대로 사용하지는 않고, 설명을 위해 약간의 수정을 하였습니다.

이번 포스트의 전체 목차는 크게 4가지로 구성되어 있습니다.

  1. Data Column & Row
  2. Tweet Words Distribution
  3. Tweet Stop Words Distribution
  4. Tweet Most Common Words Distribution

1. Data Column & Row

먼저 데이터를 살펴보도록 하겠습니다.

Columns
  • id – a unique identifier for each tweet
  • text – the text of the tweet
  • location – the location the tweet was sent from (may be blank)
  • keyword – a particular keyword from the tweet (may be blank)
  • target – in train.csv only, this denotes whether a tweet is about a real disaster (1) or not (0)

총 5개의 칼럼으로 이루어져 있는데요, id는 각각의 트위터 레코드의 식별자이고, text는 트윗 텍스트, location은 트위터를 보낸 장소, keyword는 트윗의 특정 키워드, target은 트위터가 실제로 재난인지 (1) 아닌지 (0) 를 표시한 것입니다.

location과 keyword는 경우에 따라 null일 수 있고, target은 트레이닝 데이터에만 있습니다. 참고로 캐글 컴퍼티션의 경우 예측하려는 목표 칼럼은 트레이닝 데이터에만 표시가 되어있고, 테스트 데이터에는 그 부분은 비어있어서 컴퍼티션 참여자가 직접 그 부분을 채워서 제출하면 정답은 알 수 없지만 score를 통해 자신이 제출한 답의 정확성을 가늠할 수 있게 되어있습니다.

tweet = pd.read_csv('../input/nlp-getting-started/train.csv')
test = pd.read_csv('../input/nlp-getting-started/test.csv')
tweet.head(3)

pandas 라이브러리를 통해 상위 3개의 레코드를 살펴보면 다음과 같은 데이터가 보입니다. 여기서 tweet은 트레이닝 데이터이고 test는 테스트 데이터입니다. head()는 디폴트로 상위 5개의 레코드를 보여주는데 예시에서는 괄호 안에 3을 명시함으로써 상위 3개의 데이터를 출력합니다.

keyword, location 정보는 비어있고 target의 값은 모두 1로 재난에 대해서 얘기하고 있는 트윗입니다. 내용을 읽어보아도 earthquake (지진), Forest fire (산불), shelter (대피소) 등 재난과 관련성이 깊은 단어들입니다.

print('There are %d rows and %d columns in train' %(tweet.shape[0],tweet.shape[1]))
print('There are %d rows and %d columns in train' %(test.shape[0],test.shape[1]))

트레이닝 데이터 tweet과 테스트 데이터 test 데이터 프레임에서 각각의 row와 column 수를 출력해보면, 다음과 같이 나옵니다. 트레이닝 데이터의 경우 7613개의 row와 5개의 column으로 이루어져있고, 테스트 데이터는 3263개의 row와 4개의 column 입니다. test 칼럼이 4개인 이유는 target 칼럼이 없기 때문입니다. 즉, 정답지는 트레이닝 데이터에만 있고 테스트 데이터는 없습니다.

value_counts()를 통해 전체 데이터에서 target 칼럼에 있는 1,0의 갯수가 어떻게 되는지 알 수 있고, 이를 barplot으로 그리면 다음과 같습니다.

x = tweet.target.value_counts()
sns.barplot(x.index,x)
plt.gca().set_ylabel('samples')

0 (재난이 아님) 의 경우 1 (재난임) 일 때보다 더 많은데요, 실제 세계에서 트윗 전체를 본다면 0이 1보다는 훨씬 더 많지 않을까 싶습니다. 즉, 재난이라는 비상사태에 대해서 말하는 트윗보다는 다른 주제에 대한 트윗의 비중이 높을 것 같습니다.

2. Tweet Words Distribution

이제 한 트윗당 글자 수의 분포를 살펴보겠습니다.

fig,(ax1,ax2)=plt.subplots(1,2,figsize=(10,5))
tweet_len = tweet[ tweet['target']==1 ]['text'].str.len()
ax1.hist(tweet_len,color='red')
ax1.set_title('disaster tweets')
tweet_len = tweet[ tweet['target']==0 ]['text'].str.len()
ax2.hist(tweet_len,color='green')
ax2.set_title('Not disaster tweets')
fig.suptitle('Characters in tweets')
plt.show()

재난에 관한 트윗이나, 재난과 관련되지 않은 트윗 모두 120~140 글자 수를 주로 갖고 있는 것을 위 분포를 통해 볼 수 있습니다.

이제 한 트윗 안에 단어가 몇 개 들어있는지 살펴보겠습니다.

fig,(ax1,ax2)=plt.subplots(1,2,figsize=(10,5))
tweet_len = tweet[ tweet['target']==1 ]['text'].str.split().map(lambda x: len(x))
ax1.hist(tweet_len,color='red')
ax1.set_title('disaster tweets')
tweet_len = tweet[ tweet['target']==0 ]['text'].str.split().map(lambda x: len(x))
ax2.hist(tweet_len,color='green')
ax2.set_title('Not disaster tweets')
fig.suptitle('Words in a tweet')
plt.show()

코드를 좀 더 설명하자면, tweet==0]['text'].str.split() 은 각 트윗에 있는 단어 스트링을 기준으로 split을 한 리스트입니다. ['I', 'love', 'fruits'] 처럼 I love fruits 라는 원 문장을 단어 string 기준으로 쪼갠 리스트입니다. 여기에 map(lambda x: len(x)) 을 적용하면 한 트윗 안에 단어가 몇 개 있는지 알 수 있습니다. ['I', 'love', 'fruits']의 경우는 한 트윗 안에 단어가 3개 있습니다.

전체적으로 보면, 한 트윗 안에 주로 10 ~ 20개 단어 정도 있는 것을 볼 수 있습니다.

한 트윗 내 개별 단어의 길이를 살펴보겠습니다.

fig,(ax1,ax2)=plt.subplots(1,2,figsize=(10,5))
word = tweet[ tweet['target']==1 ]['text'].str.split().apply(lambda x : [len(i) for i in x])
sns.distplot(word.map(lambda x: np.mean(x)),ax=ax1,color='red')
ax1.set_title('disaster')
word = tweet[ tweet['target']==0 ]['text'].str.split().apply(lambda x : [len(i) for i in x])
sns.distplot(word.map(lambda x: np.mean(x)),ax=ax2,color='green')
ax2.set_title('Not disaster')
fig.suptitle('Average word length in each tweet')

Diaster 트윗과 Not Diasaster의 모두 주로 대략 length가 5~6 정도 되는 단어들이 많은 것을 알 수 있습니다.

3. Tweet Stop Words Distribution

이제 stop word가 얼마나 많은지 살펴보려고 하는데, 그 전에 corpus, 말뭉치 리스트를 만들어주는 함수를 정의하여 사용할 것입니다.

def create_corpus(target):
    corpus=[]
    
    for x in tweet[ tweet['target']==target ]['text'].str.split():
        for i in x:
            corpus.append(i)
    return corpus

이 함수를 살펴보면, 특정 target 칼럼의 트윗을 string 기준으로 split해서 단어 리스트를 만든 후에, 그 리스트 안에 들어있는 개별 단어를 corpus 라는 리스트에 append를 합니다. 그렇게 해서, 예를 들어 target이 0인 트윗들에 들어있는 모든 단어를 corpus라는 리스트에 전부 append를 하여 target이 0일 때의 전체 단어 리스트를 만드는 것입니다. corpus = create_corpus(0) 이 바로 target이 0인 (즉, 재난이 아닌 경우) 트윗들에 들어있는 모든 단어 리스트를 생성하는 것입니다. 여기에다가 dic이라는 딕셔너리를 생성하여, 만약 corpus 안에 있는 word가 stop이라는 nltk.corpus 말뭉치에서 영어 stop words set 안에서 발견되면, dic에서 word 키를 기준으로 value를 1씩 증가시켜서 해당 word가 전체 단어 리스트에서 얼마나 있는지 카운트를 합니다.

corpus=create_corpus(0)

dic = defaultdict(int)
for word in corpus:
    if word in stop:
        dic[word]+=1
        
top=sorted(dic.items(), key=lambda x:x[1],reverse=True)[:10] 

참고로 영어 stop words는 from nltk.corpus import stopwords >> stop=set(stopwords.words('english')) 이렇게 정의해서 얻어지는 set 입니다. Python에서 set이란 중복되는 값이 없고, 정렬되지 않는 자료형입니다. 즉, stop이라는 set에는 중복되지 않고 정렬되지 않은 영어 stop words가 들어있습니다.

여기서 top은 stop words dictionary를 value를 기준으로 내림차순으로 정렬하여, 그 중 위에서 10개를 뽑은 리스트입니다. top을 그래프로 나타내면 다음과 같은데, the, a, to 등 재난 여부를 판단할 때 의미없는 단어들의 분포 수를 알 수 있습니다.

x,y = zip(*top)
plt.bar(x,y)

target 1인 트윗들도 위와 같은 방식을 통해서 stop words의 분포를 살펴볼 수 있습니다.

이제 punctuation 문자의 분포를 살펴보겠습니다.

plt.figure(figsize=(10,5))
corpus = create_corpus(1)

dic = defaultdict(int)
import string
special = string.punctuation
for i in (corpus):
    if i in special:
        dic[i]+=1
        
x,y=zip(*dic.items())
plt.bar(x,y)

이번에는 target이 1인 트윗들의 총 단어 리스트에서 특수 문자 분포를 살펴보았는데요, 보시면 - 라는 특수문자가 제일 많습니다. target이 0인 경우도 비슷한 방식으로 해보면 - 라는 특수문자가 제일 많습니다.

4. Tweet Most Common Words Distribution

이제 가장 흔한 단어를 살펴보겠습니다. 우선 target이 0인 트윗들의 전체 단어 리스트에서 가장 많은 단어 40개를 고르고, 그 중에서 stop word와 punctuation이 아닌 단어만 뽑아서 barplot을 그려보면 다음과 같습니다.

corpus = create_corpus(0)
counter = Counter(corpus)
most = counter.most_common()
x=[]
y=[]
for word,count in most[:40]:
    if (word not in stop and word not in special) :
        x.append(word)
        y.append(count)

주로 재난 여부와 무관한 단어들이 전체 단어에서 많은 비중을 차지하는 것을 알 수 있습니다. 데이터 클렌징 대상으로 파악될 수 있습니다.

여기까지 데이터 탐색을 해보았고, 다음 포스트에서 Ngram 분석을 진행해보도록 하겠습니다. Ngram이란 n개의 연속적인 단어 나열입니다. 예를 들어, “The book is really interesting” 이라는 문장이 있을 때, n=2인 bigrams로 구분하면 “The book”, “book is”, “is really”, “really interesting”이 됩니다. 다음 포스트는 n=2인 Ngram 분석에 대한 내용입니다.

긴 글 읽어주셔서 감사합니다 🙂