반응형

**단계구분도(choropleth)

=>지도에 색상을 추가해서 시각화하는 것

=>데이터와 영역의 경계를 나타내는 json파일이 필요

=>우리나라 영역의 경계는  southkorea-maps에서 확인이 가능

=>단계구분도 각 영역의 크기를 왜곡해서 모두 동일한 크기로 보여주도록 하는 것은 엑셀이나 svn를 이용해서 작업합니다.

=>우리나라 경계 관련 데이터 : http://github.com/southkorea/southkorea-maps

 

southkorea/southkorea-maps

South Korea administrative divisions in ESRI Shapefile, GeoJSON and TopoJSON formats. - southkorea/southkorea-maps

github.com

 

#작업 디렉토리 확인

import os

print(os.getcwd())

 

##경기도인구데이터.xlsx 파일과 경기도행정구역경계.json

#파일을 이용한 단계구분도 만들기

import pandas as pd

 

#데이터 읽기

#index_col->인덱스로 된다;

df = pd.read_excel('./Desktop/data/경기도인구데이터.xlsx',index_col ='구분')

#인덱스에 str함수를 적용해서 문자열로 자료형을 변경

#인덱스를 숫자 자료형으로 만들면 인덱스가 행의 위치인지

#인덱스인지 혼동이 올수 있습니다.

#loc[인덱스],iloc[행의위치]

#컬럼의 이름들도 문자로 만드는 것이 좋습니다.

#컬럼의 접근: 데이터프레임[컬럼이름], 데이터프레임.컬럼이름

#컬럼의 이름이 숫자이면 데이터프레임.컬럼이름은 사용할 수 없습니다.

#이름은 문자열로 만드는 것이 좋다.

 

#컬럼의 이름들을 문자열로 변경

df.columns = df.columns.map(str)#데이터 자체는 특별한 변화가 없다.

print(df.head())

 

 

#json파일 읽기

import json

 

#UnicodeDecodeError: 'cp949' codec can't decode byte 0xec in position 149: illegal multibyte sequence

#cp949 utf-8로 변경할 수 없다.

#파일의 경우는 open 함수에 파일의 경로를 대입해야하고 url이면 바로 입력해도 됩니다.

geo_data = json.load(open('./Desktop/data/경기도행정구역경계.json',encoding='utf-8-sig'))

print(geo_data)

 

 

#지도 만들기 -기본 패키지가 이님 그래서 설치를 해야 한다.

import folium

#zoom_start 작으면 축수 한다.

g_map = folium.Map(location=[37.550,126.9800],zoom_start=9)

 

 

 

#단계구분도 만들기

#fill_opacity 1.0은 안부이고 0.0은 투명이 된다. 찐하게 하면 도시이름이 안보인다.

#line_opacity 경계선

#threshold_scale 숫자구분하기 위한 숫자 리스트

#key_on json파일에서 각 영역의 이름을 나타내는 프로퍼티를 지정한다 .데이터  feature.properties.name 만드는 사람이 정한다.

#YlOrRd . YlGn

folium.Choropleth(geo_data=geo_data, data=df['2017'] , fill_color= 'YlGn',fill_opacity=0.5,

                  line_opacity=0.3,

                  threshold_scale=[10000,100000,300000,500000,700000],

                  key_on='feature.properties.name').add_to(g_map)

 

g_map.save('g_map.html')

 

**pandas의 시각화

=>pandasmatplotlib의 기능 일부분을 소유

=>Dataframe이나 Series객체를 가지고 plot이라는 메소드를 호출하면 그래프를 그릴 수 있습니다.

=>kind 옵션에 그래프 종류를 설정하면 됩니다.

line:선그래프

bar: 수직 막대 그래프

barh:수직 막대 그래프

hist: 히스트그램

box:상자그래프

kde: 커널 밀드 그래프

area: 면적 그래프

pie: 원 그래프

scatter: 산포도 - 산점도

hexbin:고밀도 산점도 그래프

=>세밀한 옵션 조정이 안되서 데이터 탐색을 할 때는 사용하는 경우가 있기는 하지만 시각화하는 용도로는 잘 사용하지 않음

 

 

데이터 없고 + 1 =>NaN

add(NaN, 1, fill_value = 0) NaN0으로 바꾼다.

tatanic은 인터넷에서 가져온다. 보안문제 때문에 외부망에서 못 가져온 경우가 종종 있다.

대기업하고 금융은 폐쇄망이다.

 

**데이터 가공

=>패키지가 제공하는 데이터는 load_dataset('데이터이름')을 이용하면 데이터프레임이나 패키지에서 제공하는 별도의 클래스 타입으로 데이터가 다운로드 됩니다.

인터넷이 안되면 데이터를 사용할 수 없습니다.

=>대기업이나 금융기관은 인터넷은 되지만 데이터는 함부로 다운로드 받거나 설치할 수 없도록 설정된 경우가 있습니다.

이런 경우에도 데이터는 다운로드가 안됩니다.

 

 

 

문자열은 object 로 나온다.

object 99%가 문자열이다.

category 범주형 라디오 버튼을 생각하면 된다. 남자/여자

범주형

None ->info()print()안해도 된다. return 값이 None이여서

<class 'pandas.core.frame.DataFrame'>

RangeIndex: 891 entries, 0 to 890

Data columns (total 15 columns):

survived       891 non-null int64

pclass         891 non-null int64

sex            891 non-null object

age            714 non-null float64

sibsp          891 non-null int64

parch          891 non-null int64

fare           891 non-null float64

embarked       889 non-null object

class          891 non-null category

who            891 non-null object

adult_male     891 non-null bool

deck           203 non-null category

embark_town    889 non-null object

alive          891 non-null object

alone          891 non-null bool

dtypes: bool(2), category(2), float64(2), int64(4), object(5)

memory usage: 80.6+ KB

None

 

1.셀의 데이터 수정

=>replace 이용

=>첫번째 매개변수로 원본 데이터를 대입하고 두번째 매개변수로 수정할 데이터를 설정

=>dict 로 원본 데이터와 수정할 데이터를 설정해도 됩니다.

replace(1,2 )=>12로 바꿔진다.

replace({1:2})

=>원본 데이터에 정규식을 사용할 수 있느데 이 경우에는 regex = True를 추가

텍스트 마이닝을 하고자 하는 경우에는 정규식을 학습할 필요가 있습니다.

=>여러 개의 데이터를 수정하고자 하는 경우에는 list로 대입해도 됩니다.

 

 

 

2.결측치 연산

=>결측치: 존재하지 않는 데이터로 파이썬에서는 None이고 numpy에서는 numpy.NaN으로 표현

=>결측치와이 연산 결과는 None입니다.

=>read_csv메소드에는 na_values 옵션을 이용해서 None으로 처리를 list를 대입할 수 있습니다.

 

 

1)Dataframenull관련 메소드

=>isnull(): None데이터인 경우는 True 그렇지 않으면 False

notnull()은 반대

=>dropna():  NaN값을 소유한 행을 제거하고 axis = 1 옵션을 추가하면 열을 제거

how매개볒ㄴ수에  all을 대입하면 모든 데이터가 None경우만 제거

thresh 매개변수에 정수를 대입하면 이 개수보다 많은 None을 가진 경우만 제거

=>fillna():NaN값을 소유한 데이터를 다른 값으로 치환하는 함수

값을 직접 입력할 수 있고 methon매개변수에 ffill등과 같은 값을 설정해서 앞이나 뒤의 값으로 채울 수 있습니다.

 

2)결측치 처리 방식

=>결측치가 많은 열의 경우는 열을 제거합니다.

=>결측치가 아주 많지 않은 경우는 결측치를 가진 행만 제거합니다.

=>결측치를 가진 데이터를 삭제하기 애매한 경우(결측치가 몇개 안되거나 어쩔수 없이 결측치가 발생하는 경우)에는 결측치를 다른 값으로 치환을 합니다.

설문조사

mcar,mar- >지워도 된다.

nmar->누락된 경우가 있어서 함부로 삭제하면 안된다.

 

#수치연산 과 선형대수 그리고 ndarray라는 자료구조를 가진 패키지

import numpy as np

#Series Dataframe이라는 자료구조를 가진 패키지

import pandas as pd

#샘플 데이터와 시각화를 위한 패키지

import seaborn as sns

 

#titanic데이터 가져오기

titanic = sns.load_dataset('titanic')

 

#데이터 탐색

print(titanic.head())

print()

print(titanic.info())

 

 

#NaN이 존재하는 지 확인

#titatnic에서 앞 쪽 10개의 데이터가 NaN을 포함하는 지 확인

print(titanic.head(10).isnull())

 

#titanic에서 NaN을 포함함 행의 개수를 파악

print(titanic.isnull().sum(axis = 0))

print(titanic['age'].isnull().sum(axis = 0))

 

 

#NaN 300개 이산인 열을 제거

#axis = 1 0은 행

#NaN이 넘 많아서 제거

titanic.dropna(thresh = 300, axis = 1 , inplace = True)

print(titanic.info())

 

 

#age 열의값이 NaN인 행을 제거 - 아주 많지 않으면 행을 제거

titanic.dropna(subset=['age'],how='any', axis= 0, inplace = True)

print(titanic.info())

 

다른 값으로 치환하는 경우에는 최빈값(가장 자주 나오는 값),평균, 중간값 등을 사용하는 방식이 있고 머신러닝을 이용해서 가장 비슷한 데이터의 값으로 채우는 방법이 있습니다.

머신러닝을 이용해서 값을 채우는 것이 가장 좋지만 이 방법은 시간이 오래 걸리기 때문에 데이터가 아주 많은 경우는 사용하기가 곤란합니다.

범주형 데이터는 평균, 중간값 사용할 수 없다.

평균, 중간값 숫자 사용한다.외곡값이 있을 수 있다.

 

 

3)결측값 대치

=>사이킷 런의 SimpleImputer클래스를 이용해서 최빈값이나 평균 및 중간값 등으로 채울 수 있습니다.

 

#titanic데이터 다시 가져오기

titanic = sns.load_dataset('titanic')

print(titanic['embarked'][820:830])

 

#직접 NaN값을 다른 값으로 대체

#inplce = True 주면 그냥하고 안주면 return 받아야 한다.

#표 형태의 데이터를 가져온 경우 셀 병합이 된 경우에 사용

titanic['embarked'].fillna(method= 'ffill',inplace = True)

print(titanic['embarked'][820:830])

 

#사이컷 런을 이용해서 결측치 채우기

features = np.array([[100],[200],[300],[500],[40],[np.NaN]])

 

 

#중간 값으로 채우는 imputer생성  200으로 채워진다.

from sklearn.impute import SimpleImputer

imputers = SimpleImputer(strategy = 'median')

 

features_imputed = imputers.fit_transform(features)

print(features_imputed)

 

fancyimpute 설치 도중 아래와 같은 에러가 발생하면 :

Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": https://visualstudio.microsoft.com/downloads/

 

 

c++은 운영체제마다 소스가 다르다.

 

google.com에서 Visual C++ 14.0 재배포 패키지를 검색해서 다운로드 받은 후 설치하고 다시 설치하면 됩니다.

이 에러는 wINDOWS에서만 발생

패키지를 만들 때 Visual C++에서 만들어서 배포를 해서 이런 현상이 발생

 

#KNN(분류) 알고리즘을 이용한 결측치 세우기

#분류 알고리즘 거리를 계산해서 분류하는 것

#fancyimpute 설치를 하고 실행

#KNN을 사용하지 않고 다른 머신러닝의 분류 알고리즘을 사용해도 됩ㄴ디ㅏ.

#존재하는 값이 나올 수 있고 존재하지 않는 값이 나올 수 있다.

from fancyimpute import KNN

features = np.array([[200,300],[300,500],[400,410],[205,np.NaN]])

features_imputed = KNN(k = 5, verbose = 0).fit_transform(features)

print(features_imputed)

삭제후 2015만 있으면 된다. =>오류 난다.

visual studio 설치후 하기

pip install fancyimpute

 

**windows에서 python실행 시 라이브러리가 설치되지 않는데

visual c++ 14.0 이 설치되어 있어야 하는데 설치되어 있지 않다는 에러 메시지가 출력되는 경우

-visual studio 2015 재배포 패키지 설치 안됨

-visual studio 2015 build 설치 잘 안됨

-visual studio 최신 버전 설치

 

xamanin을 가지고 os등 할 수 있다.

유니티 : 게임

c++ c#이다.

 

cloud-> aws

 

 

3. 중복데이터 처리

=>하나의 데이터 셋에서 중복된 데이터가 존재한다면 제거하는 것이 좋다.

동일한 데이터가 여러개 ㅇ존재하면 머신러닝이나 딥러닝을 수행할 때 결과가 왜곡 될수 있다.

=>관련함수

           duplicated():데이터의 중복여부를 리턴해주는 메소드

           drop_duplciated():중복된 데이터 제거

기본은 모든 컬럼의 값이 같은 경우에 제거

subset은 옵션에 컬럼이름의 list를 대입하면 컬럼이름에 해당하는 값들이 같은 경우에 제거

첫번째 데이터를 남기고 나머지 데이터를 삭제한느데 keep 옵션을 이용하면 다른 데이터를 남길 수 도 있습니다.

=>이런 상황은 여러 개의 데이터셋을 하나로 합칠 때 많이 발생합니다.

 

#중복 데이터 처리

df = pd.DataFrame([['차범근','크루이프','차범근'],['대한민국','네덜란드','대한민국']])

df = df.T

print(df)

 

#중복된 데이터 확인

print(df.duplicated())

 

#중복된 데이터 제거

df.drop_duplicates(inplace = True)

print(df)

 

**데이터 표준화

=>여러 곳에서 수집한 데이터들은 단위나 대소문자 구분 또는 약자 활용등으로 인해 바로 사용하기 어려운 경우가 발생할 수 있습니다.

=>이런 경우에는 단위나 대소문자 구분등을 통일할 필요가 있습니다.

=>이런 작업을 표준화라고 합니다.

 

1.단위가 다른 경우에는 반드시 단위는 통일을 시켜야 합니다.

연로비에서  미국단위와 한국단위가 달라서 통일 해야 한다.

 

 

2.자료현 변환

=>데이터를 수집할 때는 자료형을 고려하지 않고 수집을 하지만 분석이나 머신러닏등에 적용을 할 때 적용하고자 하는 알고리즘에 맞게 데이터의 자료형을 수정을 해야 합니다.

분류를 할 때는 숫자 데이터가 아니라 범주형이 필요하고 머신러닝에서 거리를 계산할려고 하는 경우에는 문자 데이터의 경우는 거리 계산이 안되므로 숫자 데이터로 번환을 해야 합니다.

자료형 변환은 astype('자료형')으로 가능

 

 

#auto-mpg.csv파일의 내용을 가져오기

df = pd.read_csv('./Desktop/data/auto-mpg_1.csv', header = None)

df.columns = ['mpg','cylinders','displacement','horsepower','weight','acceleration','model year','origin','name']

print(df.head())

print(df.info())

 

 

#origin의 경우는 생상 국가

#1 이면 미국 2이면 유럽 3이면 일본

#현재 데이터는 1,2,3 숫자 형태로 존재

#origin데이터를 문자열로 치환하고 자료형을 category로 변환

#자료형 변환은 astype('자료형')으로 가능

 

#문자열로 치환

df['origin'].replace({1:'미국',2:'유럽',3:'일본'},inplace = True)

print(df.head())

print(df['origin'].dtypes)

 

#데이터를 범주형 (category)으로 변환

df['origin'] = df['origin'].astype('category')

print(df['origin'].dtypes)

 

 

#문자열로 변환

df['origin'] = df['origin'].astype('str')

print(df['origin'].dtypes)

 

 

 

 

3.연속형 데이터의 이산화

=>연속적인 데이터를 가지고 분류를 한다면 데이터 변환없이 하게 되면 그룹의 종류가 너무 많아집니다.

이런 경우에는 연속적인 데이터를 몇 개의 구간으로 이산화를 애야 합니다.

이를 구간분할(binning)이라고 합니다.

=>pandascut함수를 이용

x에 데이터 배열을 설정

bins에 경계할 배열을 설정

labels애 변환할 이산 데이터 배열을 설정

include_lowestTrue를 설정하면 첫번째 경계값을 포함

 

 

print(df['horsepower'])

print(df['displacement'])

 

 

#displacement를 대형, 중형, 소형으로 새로운 컬럼 만들기

count, bin_dividers = np.histogram(df['displacement'], bins= 3)

print(bin_dividers)

 

 

#치환할 데이터 배열 만들기

bin_names = ['소형','중형','대형']

 

#치환

df['차량분류'] = pd.cut(x = df['displacement'], bins = bin_dividers, labels = bin_names, include_lowest=True)

print(df['차량분류'])

 

 

2)numpy digitize이용

=>첫번째매개변수는 분할할 데이터

=>bins에 구간을 분할할 값의 list를 대입

=>right 에 구간을 포함할 지 여부를 bool로 설정

=>각 구간에 0,1,2형태 이 일련번호를 배정해서 그 결과를 배열로 리턴

 

age = np.array([[30],[40],[29],[50],[60]])

#40을 기준으로 분할 -경계값이 다음 그룹으로 분할

print(np.digitize(age, bins=[40]))

print(np.digitize(age, bins=[30,50]))

#right를 이용하면 경계값이 아래 그룹으로 분할

print(np.digitize(age, bins=[30,50], right = True))

 

 

3)여러 개의 열로 구성된 데이터를 이산화 해야 하는 경우

=>군집 분석 알고리즘을 이용

 

#여러 개의 열로 구선된 데이터의 이산화

sample = np.array([[30,30],[40,70],[30,60],[25,56],[30,20],[50,60]])

df =  pd.DataFrame(sample)

print(df)

 

#KMeans 군집 분석을 위한 라이브러리

from sklearn.cluster import KMeans

#군집 분석 객체 생성

cluster = KMeans(3,random_state = 0)

#데이터를 가지고 훈련

cluster.fit(sample)

#predict  예측  - 군집

df['group'] = cluster.predict(sample)

print(df)

 

**범주형 데이터의 사용

1.원 핫 인코딩

=>카테고리를 나타내는 문자열 형태의 데이터는 머신러닝에 바로 사용할 수 없습니다.

머신러닝의 대다수 알고리즘은 숫자 데이터에서만 동작ㅇ르 수행하기 때문입니다.

카테고리 형태의 데이터를 특성의 소유 여부만을 나타내는 01로 변환하는 작업을 원핫 인코딩이라고 합니다.

=>pandasget_dummies()라는 함수를 이용해서 원핫 인코딩을 할 수있는데 컬럼에 나올 수 있는 모든 값들을 조사해서 새로운 더미변수()을 만들고 속성을 소유하고 있으면 1 없으면 0으로 포기

 

 

#? 인 데이터를 NaN으로 치환하고 NaN 인 데어트를 제거

df['horsepower'].replace('?',np.NaN, inplace =True)

df.dropna(subset = ['horsepower'], how = 'any', inplace = True)

 

#데이터 형변환

df['horsepower'] = df['horsepower'].astype(float)

 

 

#horsepower 를 저출력 , 보통출력 , 고출력으로 구간 분할

#범주형 목록을 생성

bin_names = ['저출력','보통출력','고출력']

#3개로 나눌 경계값을 생성

print(df['horsepower'])

count, bin_dividers = np.histogram(df['horsepower'], bins= 3) #형변환을 안하면 오류 난다.

 

print(bin_dividers)

 

print(df['horsepower'].dtype)

 

#구간 분할

df['hp_bin'] = pd.cut(x = df['horsepower'] , bins = bin_dividers, labels = bin_names, include_lowest = True)

print(df['hp_bin'])

 

#자료형이나 변경이 안되는 데이터가 있으면 조금씩 고쳐줘야 한다.

 

#hp_bin을 원핫인코딩 - 3개의 컬럼이 생성되고

#컬럼의 이름은 저출력, 보통출력, 고출력이 됩니다.

#자신의 값과 일치하는 컬럼에만 1이 되고 나머지 컬럼에는 0이 대입

dummy = pd.get_dummies(df['hp_bin'])

print(dummy)

 

 

2. 사이킷 런의 원핫 인코딩

=>preprocessing.LabelBinarizer:하나의 특성을 원핫 인코딩

fit_transform메소드에 데이터를 대입하면 원 핫 인코딩을 해주고 그 결과를 가지고 inverse_transform 메소드에 대입하면 원래의 데이터로 환원이 됩니다.

classes_속성을 확인하면 인코딩 순서를 확인할 수 있습니ㅏㄷ.

 

=>preprocessing.MultiLabelBinarizer:여러 개의 특성을 원 핫 인코딩

 

=>preprocessing.LabelEncoder: 0부터 시작하는 정수로 변환

 

 

 

 

된장라면이다라면 된장라면 그림이 있고 크기를 같게 하고 각각 하나하나가 특성이 되서 많이 일치하는 것을 분류로 한다.

회소행렬

0000000001 00000

(10,1) => 10번째 자리만 1이다.

회소행렬 : 압축이다. 0이 아닌 것만 표현

 

검증 검증 검증 검증 검증 검증 (검증, 6)

 

 

구분만 해야 할때

 

 

범주형 데이터를 수치화 사용할 때 원핫 인코딩이라고 한다.

 

 

 

 

#사이킷 런을 이용한 원핫인코딩

from sklearn.preprocessing import LabelBinarizer

 

one_hot = LabelBinarizer()

#데이터를 정렬하기 때문에 순서를 확인

print(one_hot.fit_transform(df['hp_bin']))

#데이터를 정렬하기 때문에 순서를 확인

print(one_hot.classes_)

 

print(one_hot.inverse_transform(one_hot.fit_transform(df['hp_bin'])))

 

#여러 개의 특성을 원핫 인코딩

#1개의 데이터가 여러 개의 특성을 갖는 경우 -tuple, list

#문장의 유사도 나 상품 추천 할 때 사용

from sklearn.preprocessing import MultiLabelBinarizer

 

features = [('Java' ,'C++'),('Java','Python'),('C#','R'),('Python','R')]

        #나온 갓이 5개 종류

#alphabet 순서

one_hot =MultiLabelBinarizer()

print(one_hot.fit_transform(features))

print(one_hot.classes_)

#MultiLabelBinarizer 이 형태로 한다.

 

 

#get_dummies는 하나의 특성을 하나의 컬럼으로 생성

#값의 종류가 15ㄷ가지이면 15개의 컬럼을 생성

#컬럼을 1개로 만들고 0부터 일련번호 형태고 값을 설정

from sklearn.preprocessing import LabelEncoder

one_hot =LabelEncoder()

print(one_hot.fit_transform(df['hp_bin']))

print(one_hot.classes_)

 

 

 

3.순서가 있는 범주형 인코딩

=>순서가 있는 경우에는 replace메소드를 이용해서 수치값으로 변환

=>일반적으로 일련번호처럶 숫자를 부여하지만 특별한 경우에는 일정 비율을 연산해서 부여하기도 합니다.

=>이와 유사한 기능을 sklearnOrdinalEncoder를 이용할 수 있음

 

#순서가 있는 범주형 인코딩

from sklearn.preprocessing import OrdinalEncoder

import numpy as np

 

features = np.array([['대학민국',30],['미국',10],['뉴질랜드',25],['캐나다',20]])

# 각 컬럼의 데이터를 정렬하고 순서대로 가중치를 부여

encoder = OrdinalEncoder()

result = encoder.fit_transform(features)

print(result)

 

 

4. 범주형 데이터에서 누락된 값 대체

=>가장 자주 등장하는 값을 누락된 값에 대해

=>머신러닝 알고리즘을 이용해서 구한 값으로 대체

 

유클리티안 거리 ->

 

 

 

#머신러닝 알고리즘을 이용한 누락된 값 대체 -분류

from sklearn.neighbors import KNeighborsClassifier

 

#훈련할 데이터

X = np.array([[0,2.10,1.45],

              [1,1.18,1.33],

              [0,1.22,1.27],

              [1,-0.10,-1.45]])

 

#NaN을 가진 데이터

X_with_nan = np.array([[np.NaN, 0.87, 1.31],

                        [np.NaN , -0.67, -0.22]])

 

#분류기술 생성

clf = KNeighborsClassifier(3, weights ='distance')

 

#훈련 -1번째 이후 데이터 전체를 가지고 0번째 데이터를 예측

trained_model = clf.fit(X[:,1:], X[:,0])

# ,

#0

 

#데이터 예측

imputed_values = trained_model.predict(X_with_nan[:,1:])

print(imputed_values)

# 뷴류는 나하고 가장 가까운 것 한다. 그래서 숫자로 바꾼다.

반응형

'Study > 데이터 분석' 카테고리의 다른 글

데이터분석-8  (0) 2020.11.12
데이터분석-7  (0) 2020.11.11
데이터분석-5  (0) 2020.11.09
데이터분석-4  (0) 2020.11.08
데이터분석-3  (0) 2020.11.08

+ Recent posts