info) 서브세션 2주차


[문제4]

  • 문제3의 calcTwoCharactersFromString()을 수정하여, calcTwoCharactersFromStringV2()를 작성합니다.
  • 수정된 동작은, 입력 파라메타 중 두번째와 세번째 글자가 같은 경우, result를 정수 -1로 리턴 합니다.

[풀이]

# calcTwoCharactersFromStringV2함수 정의
def calcTwoCharactersFromStringV2(iString, iCh1, iCh2):
    if iCh1 == iCh2: 
        return -1 #iCh1과 iCh2가 같은 경우 -1 반환
    else:
        #iString에서 iCh1의 개수를 카운트
        iCh1_count = iString.count(iCh1)
        #iString에서 iCh2의 개수를 카운트
        iCh2_count = iString.count(iCh2)
        return iCh1_count + iCh2_count 
        
#2,3번째 글자가 다른 경우
calcTwoCharactersFromStringV2("You only live once", 'o', 'Y')

#2,3번째 글자가 같은 경우
calcTwoCharactersFromStringV2("You only live once", 'o', 'o')
 

[문제5]

  • 문제3의 calcTwoCharactersFromString()을 수정하여, calcTwoCharactersFromStringV3()를 작성합니다.
  • 다른 동작은 동일하며, 문제3에서는 두 개의 글자를 iCh1와 iCh2로 입력 받았으나,이번 문제에서는 두 번째 입력 파라메타(iChList)가 글자들로 이루어진 리스트로 바뀐 사항만 다릅니다.

[풀이]

def calcTwoCharactersFromStringV3(iString:str, iChList:list):
    sum = 0
    for c in iChList:
        sum += iString.count(c)
    return sum
    
#리스트 원소가 2개인 경우
calcTwoCharactersFromStringV3("You only live once", ['o', 'Y'])

#리스트 원소가 4개인 경우
calcTwoCharactersFromStringV3("You only live once", ['o', 'Y', 'c', 'on'])

 

[문제6]

  • 입력 파라메타 2개를 받아서 result 값을 리턴하는 함수 mergeAndSortTwoList()를 작성합니다.
  • 입력 파라메타는 정수를 element로 갖는 리스트 입니다.
  • 리턴 값인 result는 입력 받은 두 개의 리스트에 포함된 모든 정수들을 합쳐서 하나의 리스트로 만들며, 중복된 정수는 하나만 포함되도록 하고,작은 숫자가 앞쪽에 있고 큰 숫자가 뒤쪽에 있도록 정렬된 형태로 만들어진 리스트 입니다.

[풀이]

#mergeAndSortTwoList()함수 정의
def mergeAndSortTwoList(list1, list2):
    #리스트를 '+'연산자로 합친 후 set(집합) 타입으로 중복을 제거하고 
    #다시 리스트 타입으로 변환
    result = list(set(list1 + list2)) 
    result.sort() #sort()를 통해 오름차순 정렬
    return result
    
#리스트 2개를 변수에 설정
list1 = [10, 30, 24, 36, 93, 78, 32, 56]
list2 = [83, 6, 24, 36, 93, 10, 44]

#함수 결과 출력
mergeAndSortTwoList(list1, list2)

 

[문제7]

  • 문제 6의 내부 동작을 수정하여 다음의 mergeAndSortTwoListReverse()를 작성합니다.
  • 수정된 동작은 문제6의 리턴 값인 result 리스트의 정렬 순서가 반대로 바뀌는 것으로,리턴 값인 result는 큰 숫자가 앞쪽에 있고 작은 숫자가 뒤쪽에 있도록 정렬된 형태로 만들어진 리스트 입니다.

[풀이]

#mergeAndSortTwoList()함수 정의
def mergeAndSortTwoListReverse(list1, list2):
    #리스트를 '+'연산자로 합친 후 set(집합) 타입으로 중복을 제거하고 
    #다시 리스트 타입으로 변환
    result = list(set(list1 + list2)) 
    result.sort(reverse = True) #sort()를 통해 오름차순 정렬
    return result
    
#리스트 2개를 변수에 설정
list1 = [10, 30, 24, 36, 93, 78, 32, 56]
list2 = [83, 6, 24, 36, 93, 10, 44]

#함수 결과 출력
mergeAndSortTwoListReverse(list1, list2)
 

[문제8]

  • 입력 파라메타 2개를 받아서 result 값을 리턴하는 함수 searchMatchedCharacter()를 작성합니다.
  • 첫번째 입력 파라메타는 문자열을 element로 갖는 리스트 이며, 두번째 입력 파라메타는 글자 하나 입니다.
  • 리턴 값인 result는 첫번째 입력 파라메타의 문자열 중, 문자열의 첫번째 글자가 두번째 입력 파라메타와 같은 경우, 해당 문자열들로 만들어진 리스트이며, 문자열의 순서는 alphabet의 순서대로 정렬된 상태 입니다.
  • 예를 들어, 첫번째 문자열이 다음과 같은 경우:
    • kingdoms = ['Bacteria','Protozoa','Chromista','Plantae','Fungi','Animalia']
  • 다음과 같이 두번째 입력 파라메타를 글자 P로 주는 경우:
    • searchMatchedCharacter(kingdoms, 'P')
  • 리턴 값 result는 다음과 같습니다.
    • ['Plantae', 'Protozoa']

[풀이]

def searchMatchedCharacter(list1, str1):
    result = [] #빈 리스트 생성
    for word in list1: #문자열 리스트에서 word 하나씩 for문으로 돌리기
        if word.startswith(str1): #해당 word가 str1으로 시작되면 True 반환
            result.append(word) #True이면 해당 word를 new_list에 추가 
    result.sort()
    return result
    
kingdoms = ['Bacteria','Protozoa','Chromista','Plantae','Fungi','Animalia']
searchMatchedCharacter(kingdoms, 'P')

 

[문제9]

  • 입력 파라메타 없이 정수 10개를 생성하여 리턴하는 함수 makeRandomTenIntegers()를 작성합니다.
  • 리턴 값인 result는 정수 리스트로써, 1부터 10사이의 숫자(1과 10포함)를 랜덤한 순서로 포함 합니다.
  • 따라서 result는 1부터 10사이의 숫자를 모두 포함하는 정수 리스트이며, 정수들의 순서는 규칙없이 랜덤이여야 합니다.

[풀이]

import random
import numpy as np

def makeRandomTenIntegers():
    int10 = list(np.arange(1,11)) #1부터 10까지의 리스트 생성
    result = random.sample(int10, 10) #리스트 내 원소들을 무작위로 섞어주기
    return result
    
makeRandomTenIntegers()

원래는 random.shuffle()을 사용하여 정수 리스트를 랜덤하게 섞어 주었으나 정답 파일과 비교한 결과 오답으로 판정되어 다른 방식을 사용하였습니다. shuffle 대신 random.sample()을 사용하였는데 shuffle과 sample의 차이를 찾아본 결과 둘 다 무작위로 섞어준다는 점은 동일하지만 다음의 차이가 있습니다. shuffle은 리스트 내에서 무작위로 섞어주며 sample의 경우에는 무작위로 섞어준 새로운 리스트를 반환하고 원본은 그대로 두게 됩니다. 또한 sample은 두번째 파라미터 값을 통해 원하는 element 수를 제한할 수 있으며  string, tuple 값들에 대해서도 셔플이 가능합니다.


[문제10]

  • 문제9의 함수를 확장해서 다음의 makeRandomIntegersExtended()를 작성합니다.
  • 첫번째 변경 사항으로 입력 파라메타가 2개 이며, 첫번째 입력 파라메타(iStart)는 랜덤 값의 시작이 되는 정수이고, 두번째 입력 파라메타(iEnd)는 랜덤 값의 마지막이 되는 정수 입니다.
  • 두 개의 입력 파라메타를 받은 함수는 다음의 경우에 대해서 정수 -1을 리턴하고 수행을 중지합니다.
    • iStart 혹은 iEnd가 0보다 작거나 같은 경우
      iStart와 iEnd가 같은 경우
      iStart가 iEnd 보다 작은 경우
  • 정상적인 입력 파라메타를 받은 경우는, 리턴 값인 result를 iStart부터 iEnd사이의 숫자(iStart와 iEnd포함)를 랜덤한 순서로 생성하여 채웁니다.
  • 따라서 result는 iStart부터 iEnd사이의 숫자를 모두 포함하는 정수 리스트이며, 순서는 규칙없이 랜덤이여야 합니다.

[풀이]

def makeRandomIntegersExtended(iStart, iEnd):
    #해당 조건을 condition 변수에 대입
    condition = (iStart <= 0) or (iEnd <= 0) or (iStart == iEnd) or (iStart < iEnd)
    #codition이 True이면 -1 리턴 아니면 result 리턴
    if condition:
        return -1
    else:
        int_list = list(np.arange(iEnd, iStart+1)) #iStart와 iEnd 사이 정수 리스트 생성
        random.sample(int_list, len(int_list)) #무작위로 섞어주기
        result = int_list
        return result
        
#조건에 걸리는 경우
makeRandomIntegersExtended(1, 10)

#조건에 걸리지 않는 경우
makeRandomIntegersExtended(10, 2)

[다른 풀이]

def makeRandomIntegersExtended(iStart, iEnd):
    if (iStart == iEnd) or (iStart < 0) or (iEnd < 0) or (iStart < iEnd):
        return -1
    else:
        randomList = []
        randomInt = random.randint(iEnd, iStart)
        for i in range(int(iStart-iEnd+1)):
            while randomInt in randomList:
                randomInt = random.randint(iEnd, iStart)
            randomList.append(randomInt)
        return randomList
 
 

 

'BACS > 서브세션' 카테고리의 다른 글

[서브세션] 서브세션 3주차  (0) 2022.11.06
[BACS] 서브세션 1주차  (0) 2022.09.27

Info) 정규세션 1주차 (강원도 소방 데이터)

Problem. 왼쪽의 원본 데이터를 가공하여 오른쪽 데이터 형태로 만들기


1. 데이터 불러오기

import pandas as pd

#관할서 센터별 소방용수 데이터
fire_df =  pd.read_csv('./관할서 센터별 소방용수 데이터.csv')

#관할서 센터별 소방용수 데이터 feature설명
feature_df = pd.read_excel('./관할서 센터별 소방용수 데이터.xls')

#정답 파일: 강원도 지역별 소방용수시설 별 개수
answer_df = pd.read_excel('./강원도 지역별 소방용수시설 별 개수.xlsx')

2. fire_df 확인 및 수정 

2-1. fire_df 컬럼명 변경

#feature_df의 컬럼명 리스트로 가져오기
column_names = list(feature_df.columns)

#fire_df의 컬럼명에 추가하여 변경
fire_df.columns = column_names

2-2. 결측치 확인 및 제거

#'소방용수구분명'의 결측치 확인
fire_df['소방용수구분명'].isna().sum() 

#'소방용수구분명'이 null인 행 제거
fire_df.dropna(subset=['소방용수구분명'], axis=0, inplace=True)

3. 파생변수 '지역' Column 만들기

#군구명과 동명을 합쳐 새로운 지역 컬럼 만들기
fire_df['지역'] = fire_df['구군명']+ ' ' + fire_df['동명']

4. One Hot Encoding 적용

#fire_df에서 필요한 컬럼만 추출 
fire_df = fire_df[['지역', '구군명', '동명', '소방용수구분명']]

#원핫 인코딩 적용하여 새로운 fire_df_new 생성
fire_df_new = pd.get_dummies(data = fire_df, columns = ['소방용수구분명'])

5. Column 이름 재설정 및 순서 재배치

#fire_df_new의 컬럼명 재설정
fire_df_new.columns = ['지역', '시군구', '읍면동', '급수탑', '기타', '소화전(지상식)', '소화전(지하식)', '저수조'] 

#컬럼 순서 재설정
fire_df_new = fire_df_new[['지역', '시군구', '읍면동', '소화전(지상식)', '소화전(지하식)', '급수탑', '저수조', '기타']]

6. groupby 통해 그룹별 집계

#groupby 통해 지역별 합계로 집계
fire_df_new = fire_df_new.groupby(['지역', '시군구', '읍면동']).sum()

#인덱스 초기화
fire_df_new.reset_index(inplace=True)

#최종 결과물 확인
fire_df_new

cf) 다른 방식 (crosstab 활용)

cross_tab_prop = pd.crosstab(index=[fire_df['지역'], fire_df['구군명'], fire_df['동명']],
                             columns=fire_df['소방용수구분명'],
                             normalize= False,
                             margins = False)
cross_tab_prop.reset_index(inplace = True)
cross_tab_prop.columns = ['지역', '구군명', '동명', '소화전(지상식)', '소화전(지하식)', '급수탑', '저수조', '기타']
cross_tab_prop

Warning) 아래 코드들은 버전이 바뀌어 정상적으로 작동되지 않을 수도 있습니다. 따로 버전을 명시하지 않았기 때문에 에러가 발생되면 조금씩 수정해 나가면서 사용하시길 바랍니다. 또한, 형편없는 저의 실력으로 코드들이 다소 비효율적일 수 있음을 미리 말씀드립니다. 우연히 이 글을 보게 되신 분들은 참고해주시기 바랍니다. 

text-preprocessing-for-textanlytics.ipynb
0.31MB


STEP 4. 빈도 분석을 위한 텍스트 전처리

STEP3에서는 이전를 날리는 일이 없도록 하기 위해 제품별 url을 하나의 리스트로 묶어 반복 실행하지 않고 일부러 따로 따로 독립적으로 수집하였습니다.

(1) 라이브러리 import 및 파일 불러오기

import pandas as pd
import re

df = pd.read_csv('/content/drive/MyDrive/Github/review-topic-modeling-project/output_file/all_review.csv')
df['Review']

8개 제품에d은 같기 때문에 2개의 코드만 가져왔고 이와 같은 방식을 8번 진행했다고 보시면 됩니다. 수집 결과 아래 사진과 같이 총 8개의 csv 파일이 만들어지게 됩니다. 

(2) 특수문자 제거

df['Review'] = df['Review'].str.replace(pat=r'[^\w]', repl= r' ', regex=True)  # replace all special symbols to space
df['Review'] = df['Review'].str.replace(pat=r'[\s\s+]', repl= r' ', regex=True)  # replace multiple spaces with a single space

df

def extract_symbol(text):
    text = text.str.replace(pat=r'[^\w]', repl= r' ', regex=True)
    result = text.str.replace(pat=r'[\s\s+]', repl= r' ', regex=True)
    return result

(3) 한글 표현만 남기기

#한글 표현만 남기기 
def extract_word(text):
    hangul = re.compile('[^가-힣]') 
    result = hangul.sub(' ', text) 
    return result
print("Before Extraction : ",df['Review'][17480])
print("After Extraction : ", extract_word(df['Review'][17480]))

print("Before Extraction : ",df['Review'][1494])
print("After Extraction : ", extract_word(df['Review'][1494]))

df['Review'] = df['Review'].apply(lambda x:extract_word(x))

(4) 띄어쓰기 처리

pip install git+https://github.com/haven-jeon/PyKoSpacing.git
from pykospacing import Spacing

spacing = Spacing()
print("Before Fixing : ",df['Review'][324])
print("After Fixing : ", spacing(df['Review'][324]))
print("Before Fixing : ",df['Review'][14454])
print("After Fixing : ", spacing(df['Review'][14454]))

 

df['Review'] = df['Review'].apply(lambda x:spacing(x))

df['Review'][324]
#띄어쓰기 처리 
from pykospacing import Spacing
def extract_word(text):
    spacing = Spacing()
    result = text.apply(lambda x:spacing(x))

 

(5) 형태소 분석

!pip install konlpy

from konlpy.tag import Okt
okt = Okt()
# 리뷰 텍스트 정규화 처리
df['Review'] = df['Review'].apply(lambda x: okt.normalize(x))
df['Review']

df.to_csv('/content/drive/MyDrive/Github/review-topic-modeling-project/output_file/processed_all_review.csv', index = False)

지금까지 수집한 파일들은 output_file 폴더에 저장하였고 총 8개 파일이 있습니다. 파일들이 다 파편적으로 분리가 되어 있으면 텍스트 분석을 진행하는데 있어 번거로움이 발생합니다. 따라서, 파일들을 하나로 병합하도록 하겠습니다. 그리고 파일 병합을 진행하기 전에는 중복된 파일들이 있는지 확인하고 중복된 경우에는 제거하도록 하겠습니다.

words = " ".join(df['Review'].tolist())
words = okt.morphs(words, stem=True)
words
len(words)

실 정확하게 스크래핑을

(6) 한글자 처리 및 불용어 제거

path = '/content/drive/MyDrive/Github/review-topic-modeling-project/output_file'

one_word = [x for x in words if len(x) == 1]
one_word_set = set(one_word)
print(one_word_set)

#특정 한글자를 제외한 나머지 한글자는 drop
remove_one_word = [x for x in words if len(x) > 1 or x in ['맛', '술', '양', '짱']]
len(remove_one_word)

#각 단어별 빈도수 확인
from collections import Counter
frequent_words = Counter(remove_one_word).most_common()
frequent_words

with open('/content/drive/MyDrive/Github/review-topic-modeling-project/stopwords.txt', encoding='cp949') as f:
    stopwords_list = f.readlines()
stopwords = stopwords_list[0].split(",")
len(stopwords)

remove_stopwords = [x for x in remove_one_word if x not in stopwords]
len(remove_stopwords)

Counter(remove_stopwords).most_common()

words_frequency = dict(Counter(remove_stopwords))
words_frequency

(7) 빈출 키워드 워드 클라우드 생성

#!sudo apt-get install -y fonts-nanum
#!sudo fc-cache -fv
#!rm ~/.cache/matplotlib -rf

import matplotlib.pyplot as plt
from wordcloud import WordCloud
# jupyter notebook 내 그래프를 바로 그리기 위한 설정
%matplotlib inline

# unicode minus를 사용하지 않기 위한 설정 (minus 깨짐현상 방지)
plt.rcParams['axes.unicode_minus'] = False
import matplotlib.pyplot as plt

plt.rc('font', family='NanumGothic') 

df = pd.DataFrame(remove_stopwords, columns=['Word'])

wc = WordCloud(font_path='NanumGothic', colormap = 'summer', background_color = 'black', width=800, height=400, scale=2.0, max_font_size=250)
gen = wc.generate_from_frequencies(words_frequency)
plt.figure(figsize = (10, 8))
plt.style.use('seaborn-whitegrid')
plt.imshow(gen)
plt.grid(False)
plt.axis('off')
#wordcloud 이미지 저장
plt.savefig('/content/drive/MyDrive/Github/review-topic-modeling-project/output_file/wordcloud.png', dpi= 300)

후에는 중복된 리뷰 데이터들을 제거하겠습니다. 중복이 제거된 데이터프레임은 다시 새로운 파일로 저장하겠습니다. 

중복 제거까지 진행한 결과 최종 수집된 데이터는 19445개로 당초 예상했던 2만개에는 조금 못 미치는 정도입니다. 중복된 내용 때문에 계획한 샘플 수를 충족시키지는 못했지만 결과에는 큰 영향을 주지 않을 것 같아 그냥 진행하도록 하겠습니다. 

 

조금은 쓸데없는 과정이기는 하지만 쇼핑몰별로 수집된 리뷰 데이터의 수를 확인해보도록 하겠습니다. 앞서 프로젝트 계획 단계에서 편향되지 않은 분석 결과를 도출하기 위해 가능한 다양한 쇼핑몰에서의 리뷰를 가져오겠다고 말씀드린 적이 있습니다. 사실 리뷰의 출처가 되는 쇼핑몰이 다양할수록 리뷰 또한 다양하고 편향되지 않는다고 말할 수는 없습니다. 쇼핑몰과 리뷰 내용의 편향성은 상관관계가 높지 않을 것입니다. 하지만 한쪽 쇼핑몰의 리뷰만 가져올 경우에는 분명 해당 쇼핑몰만의 문제들이 분석 결과에 다소간 영향을 끼칠 수도 있는 있을 것입니다. 때문에 제 생각에는 쇼핑몰의 다양성을 유지하는 것이 그리 나쁜 선택은 아닌 것 같습니다. 

아래 결과를 보면 이건 쫌... 이라는 생각이 드실 것 같습니다. 생각보다 쇼핑몰간 편차가 커 보입니다. SSG닷컴이 거의 전체 샘플 데이터의 절반 이상을 차지하고 있고 11번가까지 정도만 천단위 샘플이 수집되었고 나머지는 거의 미미한 수준입니다. 한편, 쿠팡이나 마켓컬리와 같이 최근 많은 매출을 기록하고 있는 쇼핑 플랫폼의 데이터가 없는 것이 아쉬움이 남습니다. 쿠팡 같은 경우는 네이버 쇼핑과 연동이 되지 않아 따로 수집해야 되는 것으로 보입니다. 여튼 분포 결과가 샘플의 균형을 맞추기에는 다소 부족한 부분이 있지만 실제로 결과물을 가져와 보아야 정확히 알 수 있을 듯 싶습니다. 

 

다음 단계에서는 이번 프로젝트의 첫번째 핵심이라고 할 수 있는 텍스트 전처리를 진행하도록 하겠습니다. 전처리 수준에 따라 결과물의 내용이 달라질 수 있기 때문에 많은 심혈을 기울여야 될 것 입니다. 다음 스텝에서 뵙도록 하겠습니다.

 

 

 

+ Recent posts