자바스크립트(Javascript) 기록-1

전에 만들어 놓았던 카카오톡 클론코딩을 배포 하던 중 HTML과 CSS만으로는 부족해보이므로 Javascript로 기능을 추가해 보라는 강사님의 피드백에 따라 JS로 기능을 추가하기 위해 JS가 주인 클론코딩 강의를 듣고 카카오톡 클론코딩에 잘 버무려 보기로 했다.

그런데 HTML의 스크립트태그는 몇번 접해본적 있었지만 JS는 초면인지라 다른 언어들의 기초보다 문법이 복잡한지, 어떤 부분이 다른지 전혀 모르고 있었기에 기록 겸 가끔 보려는 용도로 포스팅 한다.

하루 배우고 하루 기록하는 토막글 형식이기에 한두번 만에 끝날것 같지는 않으니, 귀찮아 하지말고 매일 기록할것.

변수 선언 방식


자바에 여러개의 변수 타입이 있듯이 자바스크립트에도 변수 타입이 존재한다. 그러나 다른점이라면 자바는 변수의 크기, 타입에 따라 분류한다면 자바스크립트는 이후에 값을 수정 가능한지 아닌지로 분류한다.
일단 기본적인 변수 선언 방식은 다음과 같다.

1
2
3
const a = 1;
let b = 2;
var c = 3;

기본적으로 변수 선언 방식 이후 변수에 값을 넣고 닫아주는 형태이다.

갑자기 잡설을 풀어보자면
파이썬, 자바등을 공부할때 해당하는 언어를 설치해주어야 했는데 자바스크립트는 그렇지 않다.
다른 언어의 경우에는 버전이 올라감에 따라 이전버전의 문법을 사용하지 못하는 경우가 있다.
그러나 자바스크립트는 이전버전의 문법(const,let이 있음에도 var가 사용 가능한경우)이 여전히 혼용 가능하다.(그러나 쓰지않는걸 추천하는것 같다.)

각설하고, 총 세 개의 방식이 있는데 각각의 특징은 이렇다.

const

ES6 이후에 추가되었고 가장 기본적으로 사용되는 변수형이다, 상수(변수인데 상수;;)이며 그렇기 때문에 선언 이후에 변경이 불가하다.(할당된개체가 Array와 Objects등의 개체라면 내부의 값을 변경할 수는 있다. ) 선언할때 반드시 값을 할당해주어야 한다.

let

ES6 이후에 추가되었고 현재 사용되고 있는 가변 변수형이다, 선언 이후에도 변경이 가능하다. 선언할때 값을 할당하지 않을수 있지만 그럴경우 값은 undefined가 된다.

var

자바스크립트 초기에 사용되었던 변수형이며, 선언 이후에도 변경이 가능하다. 역시나 선언할때 값을 할당하지 않을수 있고, 이 경우 값은 undefined가 된다.

왜 let은 쓰는데 var는 쓰지 않는지, 왜 기본적으로 let이 아닌 const를 쓰는지 의문이 생길수 있다.
전자의 의문의 답은 세가지이다.

첫째는 변수명이 같아도 선언이 가능하다는것. (재선언 가능)
누군가는 장점이 아니냐 할 수 있겠지만 변수의 양이 매우 많거나, 다른사람과 협업을 한다면 이는 단점이 된다.

둘째는 호이스팅(Hoisting).
이는 변수의 정의가 선언과 할당으로 분리되는것을 말한다, 이게 무슨뜻인지는 다음 코드를 보자

1
2
console.log(name);
var name = "WJ";

이렇게 코드가 있을때 자바스크립트는 다음과 같이 해석한다.

1
2
3
var name;
console.log(name);
name = "WJ";

결국 출력은 초기화가 되지않았다는 메시지가 나오는 대신 정의되지 않았다는 뜻인 undefined 가 나오게 된다.
이는 직관적이지 못하기때문에 var보다 let을 쓰는것이 권장된다.

셋째는 const,let은 ‘블럭’수준 var는 ‘함수’수준으로 적용되기 때문이다.
이게 무슨말인지 처음엔 이해가 잘 되지 않을수 있다. (나는 그랬음..)
그러니 바로 예시를 보면

1
2
3
4
5
6
7
8
9
var a = 5;              # 전역변수 a 선언후 5 할당
console.log(a); # 5 출력

function setA() {
var a = 10; # '함수 내부'에서 지역변수 a 선언후 10 할당
console.log(a); # 10 출력
}

console.log(a); # 5 출력

전혀 이상할것 없이 당연한 예시이다. 그럼 다음예시도 한번 보자

1
2
3
4
5
6
7
8
9
var a = 5;              # 전역변수 a 선언후 5 할당
console.log(a); # 5 출력

if (true) {
var a = 10; # 'if블럭 내부'에서 지역변수 a 선언후 10 할당
console.log(a); # 10 출력
}

console.log(a); # 10 출력 <-- ???

if블럭 내부에서 지역변수를 할당 했음에도 전역변수로 선언했던 a의 값이 바뀌어 10이 되었다.

이는 위에서 말했듯 var의 범위는 ‘함수’수준으로 적용되기 때문이다.
즉, 블럭{}으로 감싸더라도 함수로 감싸진것이 아니기때문에 if블럭 내에서 재할당된 값이 출력된 것이다.

쓰면 안되는 이유 1,3번의 콜라보..

결국 요약하자면 var는 함수내부에서 쓰는것이 아니라면 전역변수와 같다는 것이다.

안쓰는데는 이유가있다

Array (배열, 리스트)

파이썬으로 처음 접했던 리스트 역시 자바스크립트에도 있다.
다음은 array를 선언하는 기본적인 방법이다.

1
const a = [1, 2, null, true, "hello"];

인덱싱은 a[2]; 처럼 가능하고, 값을 추가하고 싶을 경우 a.push(추가할 값) 해주면 해당 값이 가장 마지막의 인덱스에 추가된다.

Objects (파이썬 딕셔너리)

제목에도 적었듯이 키와 값으로 이루어진 것이 파이썬의 딕셔너리와 닮았다. 세세한기능까지 같은지는 모른다
오브젝트를 선언하는 기본적인 방법을 보면

1
2
3
4
5
6
const a = {
name : "WJ",
contry : "KR",
lang : "Javascript",
num : 123
}

처럼 선언하는데 중괄호 내부의 프로퍼티 중 왼쪽을 이름, 오른쪽을 값이라 한다.

값 출력시엔 다음처럼 하면 된다.

1
2
a.name;
a["name"];

예시에서 배열과 오브젝트 둘다 const로 선언했지만 내부의 값은 바꿀 수 있다.

function (함수)

1
2
3
function a() {
console.log("Hello!");
}

문법만 조금 다를 뿐 전체적으로 파이썬의 사용자 지정 함수나 자바의 메소드와 비슷하다.

함수호출은 a() 로 할수 있으며 결과는 Hello! 가 출력되게 된다.





Reference.

ES6의 변수 선언, const와 let - nana_log

Heroku로 HTML 페이지 배포하기

캐글 대시보드 배포 프로젝트를 마치고 꽤나 지났을때, 학원에 다니기 한참 전에 HTML과 CSS 로만 만들었던 카카오톡 클론코딩 프로젝트가 생각나 그것도 배포하는 김에 쓰는 글.

사전준비


  • Heroku 계정
  • Heroku CLI
  • Git

일단 Heroku에서 배포를 할 것이므로 Heroku 의 계정과 Heroku CLI 설치가 필요하다.
Git 도 자신의 환경에 맞추어 설치해주자. 윈도우에서 Git 설치하기

node.js가 설치되어 있는 경우에는 npm 으로 간편히 설치가 가능하다고 한다.

1
npm i -g heroku

설치가 잘 됐나 확인해보자

1
heroku -v

버전이 출력되면 잘 설치가 된 것이다.

1
heroku login

설치가 잘 완료되었으면 heroku login을 입력하자.
그러면 브라우저 창이 하나 뜨고, heroku계정을 입력하면 준비 완료.



HTML 파일을 Heroku로 배포하기


프로젝트를 로컬에서 완성했다고 바로 heroku로 배포하면 heroku가 어떤 언어로 개발한 프로젝트인지 모르기 때문에 다음과 같은 오류를 내며 구동되지 않는다.
짚히는곳 없으면 개고생 시작
코린이 입장에서 프로젝트 만들었다고 싱글벙글하다가 에러나면 진짜 아찔하다.
그래서 이런일이 없게 heroku에서 지원하는 언어들중 하나인 php 프로젝트로 인식되게 처리해 주어야 한다.
프로젝트의 루트 디렉토리에 다음처럼 html 파일로 리다이렉트 시켜주는 php 파일만 하나 만들어주면 된다.

index.php

1
2
3
<?
header('Location: /index.html');
?>

이제 파일은 모두 완료됐으니 CLI를 이용해서 heroku에 배포를 해보자.
당연히 아래의 명령을 입력하는 위치는 프로젝트의 루트 디렉토리여야 한다.

1
2
3
4
5
6
7
8
9
10
# 저장소를 만들고 커밋 (github연결과는 무관)
git init
git add .
git commit -m "first commit"

# 새로운 Heroku app 생성
heroku create

# Heroku에 push하기
git push heroku master

이렇게 하면 배포는 마무리 됐다.

이외에도 같이 필요할것같은 몇가지 명령어를 적어보자면



파일 수정 후 재배포시

1
2
3
4
git add .
git commit -m "커밋 메모"

git push heroku master

Heroku app 이름 바꾸기

heroku create로 app을 만들면 이름이 임의로 생성된다 그렇기때문에 이름을 바꾸고 싶다면 아래의 명령어를 사용하면 된다.

1
heroku apps:rename <새 이름> --app <기존 이름>

heroku app 열기

heroku app 페이지에서 open app 버튼을 통해 여는 방법도 있으나 CLI를 통해 바로 열어보고 싶다면 다음 명령어를 사용하자.

1
heroku open

heroku git 과 git 저장소 연결 명령어

본 포스팅에서는 폴더에서 app을 만들었기때문에 바로 연결되어 사용할 필요가 없었던 명령어이다.
그러나 저장소의 주소가 필요하거나 변경할 경우를 위해 기록한다.

1
2
git remote -v # 폴더와 연결된 저장소(heroku git, github 등)의 주소가 출력됨
git remote set-url heroku <변경할 git 경로> # heroku저장소의 주소를 변경할 경우 사용, 변경할 git 경로의 예)https://git.heroku.com/<app이름>.git

오류발생시 로그 출력

배포를 진행하다 보면 오류가 발생할 수 있다.
그럴 경우 로그를 출력하여 에러코드를 확인하고 영어로 구글링하여 답을 찾도록 하자.

1
2
heroku logs
heroku logs --tail




Reference.

파이썬(Python) 데이터 크롤링 기초 수업 정리

개발자나 데이터분석가에게 중요한 능력중 하나를 꼽자면 무엇일까.
자신이 진행할 프로젝트의 데이터를 수집하는 능력도 꽤나 중요하지 않을까 싶다.
그러기 위해서는 크롤링을 해야 할 때가 있는데 크롤링이란 HTML(웹페이지)를 그대로 가져와 데이터를 추출하는 행위를 말한다.
그래서 이번 포스트에서는 학원에서 배운 간단한 크롤링에대해 수업 그대로를 정리하려 한다.

사전준비


크롤링을 하기위해 일단 BeautifulSoup 라는 파이썬 라이브러리를 설치해 주어야 한다.
다음 코드를 터미널에 입력해 라이브러리를 설치한다.

1
pip install beautifulsoup4

설치가 완료되었다면 예제 HTML 문서를 하나 만들어주면 준비는 끝난다.

index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Crawl This Page</title>
</head>
<body>
<div class="cheshire">
<p>Don't crawl this.</p>
</div>
<div class="elice">
<p>Hello, Python Crawling!</p>
</div>
<div id="main">
<p>I am in main</p>
</div>
</body>
</html>

BeautifulSoup를 사용하여 크롤링

먼저 크롤링을 하기 위해 파이썬 파일을 만들어준다.

1
from bs4 import Beautifulsoup

이후 Beautifulsoup 라이브러리를 import하고 간단한 함수를 만들어 크롤링을 해보자.



HTML 불러오기

1
2
3
4
5
6
def crawling():
soup = BeautifulSoup(open("data/index.html"), "html.parser") # open의 괄호안에 크롤링할 HTML파일의 위치를 입력
print(soup)

if __name__ == "__main__":
crawling()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>

<html>
<head>
<meta charset="utf-8"/>
<title>Crawl This Page</title>
</head>
<body>
<div class="cheshire">
<p>Don't crawl this.</p>
</div>
<div class="elice">
<p>Hello, Python Crawling!</p>
</div>
</body>

HTML 파일이 soup에 저장되어 출력된 것을 볼 수 있다.



필요한 태그만 가져오기

하지만 우리는 HTML 전체가 아닌 특정 텍스트, 태그만 필요로 하기 때문에 그것들을 뽑아낼 수 있어야 한다.

1
2
3
4
5
6
def crawling():
soup = BeautifulSoup(open("data/index.html"), "html.parser")
print(soup.find("p"))

if __name__ == "__main__":
crawling()
1
<p>Don't crawl this.</p>

find()함수는 가장 앞쪽의 한 태그만을 가져온다.
이처럼 p태그가 출력되었는데 두가지 의문이 생길 것이다.
‘p태그는 두개인데 왜 한개만 출력되는가?’와 ‘<p></p>없이 텍스트만 추출 할수 없는가?’라는 것이다.
물론 가능하다.
첫번째는 find()함수를 find_all()로 바꾸면 모든 p태그가 출력되고,
두번째는 find()함수 뒤에 .get_text()를 붙여주면 태그가 제외된 텍스트만 출력이 된다.
이외에도 많은 함수가 있고, Beautiful Soup Documentation 에서 찾아볼 수 있다.



class, id로 태그 가져오기

여전히 크롤링이라기엔(기초지만) 부족하다 원하는 태그를 가져올 수는 있으나 태그가 여러개면 같이 가져올 수밖에 없기 때문이다.
그래서 이번에는 각 태그에 주어진 class와 id를 통해 크롤링하는 법에 대해 알아보자.

index.html을 보면 div태그에 각각 class와 id가 있다.
이를통해 크롤링 하는 코드를 보면

1
2
3
4
5
6
def crawling():
soup = BeautifulSoup(open("data/index.html"), "html.parser")
print(soup.find("div", class_ = "elice").find("p").get_test())

if __name__ == "__main__":
crawling()

다음과 같으며 이를 실행하면

1
Hello, Python Crawling!

위와 같이 출력된다.
만약 class가 아닌 id를 지정해서 태그를 찾고싶을 경우 find()함수 내에 class_ = 가 아닌 id = 를 입력하면 된다.
class는 언더바가 붙고 id는 붙지 않는다.

OPEN API를 통해 데이터 가져오기

open API를 통해 데이터를 가져오는 법도 간단히 알아보자.
이 포스팅은 캐글을 기반으로 진행했으며 데이터는 한국도로공사 공공데이터 포털 의 교통데이터를 이용했다.

먼저 API에 접근하기 위해 인증키를 발급 받는다.

실시간 영업소간 통행시간 페이지
이후 실시간 영업소간 통행시간 페이지 에 접속해 아래의 예제 실행하기를 클릭.

실시간 영업소간 통행시간 팝업
원하는 Request Parameter를 입력하고(필자는 key(여기서 키는 방금 발급받은 인증키), type, iStartUnitCode, iEndUnitCode만 작성) URL보기를 하면 URL이 출력되고 , 아래의 예제 실행하기를 통해 직접 볼 수도 있다.

이제, 캐글에 다음과 같이 입력해준다.

1
2
3
4
5
6
7
8
import requests
key = "발급받은 인증키"
type = "json"
url = "출력된 URL"
responses = requests.get(url)
print(responses)
json = responses.json()
json

을 입력해주면
팝업에서 보았던 예제를 직접 볼 수 있다.

필요한 정보가 있는 “realUnitTrtmVO” 항목을 가져오기 위해 다음 코드 입력

1
cars = json["realUnitTrtmVO"]

이것을 반복문을 통해 리스트, 딕셔너리 형태로 만들어준다.

1
2
3
4
5
6
7
8
9
10
data = []
for car in cars:
dic_df = {}
dic_df["date"] = car["stdDate"]
dic_df["time"] = car["stdTime"]
dic_df["destination"] = car["endUnitNm"]

data.append(dic_df)

data

아래는 아웃풋

1
2
3
4
5
6
7
8
9
10
[{'date': '20220103', 'destination': '수원신갈', 'time': '05:30'},
{'date': '20220103', 'destination': '수원신갈', 'time': '05:35'},
{'date': '20220103', 'destination': '수원신갈', 'time': '05:40'},
{'date': '20220103', 'destination': '수원신갈', 'time': '05:45'},
{'date': '20220103', 'destination': '수원신갈', 'time': '05:50'},
{'date': '20220103', 'destination': '수원신갈', 'time': '05:55'},
{'date': '20220103', 'destination': '수원신갈', 'time': '06 '},
{'date': '20220103', 'destination': '수원신갈', 'time': '06:00'},
{'date': '20220103', 'destination': '수원신갈', 'time': '06:05'},
{'date': '20220103', 'destination': '수원신갈', 'time': '06:10'}]

이렇게 딕셔너리로 구성된 리스트가 생성되었다.
이것을 Pandas Dataframe으로 변환후 엑셀시트로 출력하려면 다음과 같이 하면 된다.

1
2
3
import pandas as pd
df = pd.DataFrame(data) # 판다스 데이터프레임으로 변환
df.to_csv("temp.csv",index=False,encoding="euc-kr")

이렇게하면 설정된 곳으로 캐글에서 데이터프레임이 다운로드된다.

콜백(Callback)함수의 사용법

Plotly 를 사용하여 대시보드를 만드는 동안에 직면했던 여러 문제들중 해결에 꽤 오랜시간이 걸린 문제가 있었다.
바로 콜백함수에 대한 부분인데 처음보게된 콜백함수가 데코레이터가 적용된 그런 함수였기 때문에 굉장히 헷갈렸다.

콜백함수


콜백함수란 다른함수의 인자로써 이용되는함수, 어떤 이벤트에 의해 호출되어지는 함수를 말한다.
함수가 다른함수의 인자로 사용될 수 있다니, 잘 이해가 되지 않을 수 있다.

내가 접했던 콜백함수를 가져와 보면 이렇다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@app.callback(
Output('id_fig_age', 'Figure'),
Input('country-filter', 'value'))
def age_chart_func(value):
obj = px.bar(data,
x=data[data['Q3'] == value]['Q1'][1:].value_counts().sort_index().index,
y=data[data['Q3'] == value]['Q1'][1:].value_counts().sort_index().values,
)
obj.update_traces(hovertemplate='%{x}: %{y:.0f}',
marker_color='#E08E79',
marker_line_width=0,)
obj.update_layout(paper_bgcolor=colors['content-background'],
font_color=colors['text'],
plot_bgcolor=colors['plot_background'],
autosize=True)
obj.update_xaxes(title_text='Age Distribution')
obj.update_yaxes(title_text='Counts')
return obj

전체코드의 일부분이지만 함수를 설명하는데에는 문제가 없다.

먼저 가장 위에 있는 @app.callback가 무슨뜻인지 이해하지 못한다면 데코레이터 에 대해 간략히 보고 오면 좋다.
그리고 Output과 Input은 dash.dependencies 라이브러리에서 import 한 것인데, 기능을 살펴보기위해 코드대로 해석하자면

Input의 괄호안의 첫번째 인자(country-filter)와 ID가 같은 항목을 찾아 두번째 인자(country-filter 의 파라미터 value)를 아래에 있는 함수의 인자로 넣어주며, 인자를 통해 함수를 실행 후 나온 리턴을,
Output의 괄호안의 첫번째 인자(id_fig_age)와 ID가 같은 항목의 두번째 인자(Figure)로 입력되는 것이다.

위 코드에서 복잡해 보일 수 있는 부분인 px.bar ~ obj.update_yaxes 까지는 그냥 그래프를 그려주는 함수라고 생각하면된다.

이때 이 함수를 모두 실행하고 리턴하는 값인 obj 가 반환되어 Output인 ‘id_fig_age’의 ‘Figure’속성으로 입력되고 ‘id_fig_age’는 함수에서 작성된 그래프를 ‘Figure’로 갖게되어 화면에 표현해준다.

git branch 기초 (추가,이동,삭제 등)

heroku를 통해서 대시보드 배포를 하려고 애를 쓰던중에 끝도없는 에러와 마주쳐서 github Repo를 지웠다 만들었다 하기를 열번가량 반복하다가 현타가 왔다.
강사님이 주신 URL 대로하면 가장 기본적인 배포는 가능하니 처음것을 백업해놓고 이것저것 고쳐가며 써보자 하는 생각이 들자 git이 애초에 그런(버전 관리) 프로그램이었다는 게 생각나 찾아보고 포스팅하게 되었다.

그래서 branch가 뭔데?


branch는 git에서 독립적으로 어떠한 작업을 진행하기 위한 개념이다.
각각 나누어진 branch는 서로 영향을 주거나 받지 않기 때문에 여러작업을 동시에 진행해볼 수 있다.
또, 이렇게 나누어진 branch는 다른 branch와 병합하거나 덮어씀으로써 작업한 내용을 새로운 하나의 브랜치로 모을 수 있다.

branch의 설명도

위의 그림처럼 branch에서 서비스추가, 버그수정 등을 하고 main branch에 병합시키는 것이다.
branch 에서 작업을 하다가 문제가 되어도 폐기하고 main branch에서 새로 branch를 만들면 되니 원본 손실의 위험도 없다.

그럼, 어떻게 쓰는건데?


branch의 명령어는 꽤나 다양하지만 이 포스팅에서는 추가,이동,삭제등의 기본적인 것만들 다루겠다.(추후 포스팅 예정)

branch 확인

branch를 추가, 이동, 삭제하기 전에 Repo에 어떤 branch가 있는지 확인해야한다.(동명의 branch는 만들어지지 않는다.)
확인을 하기 위해서는 Repo의 디렉토리에서 터미널이나 git bash로 다음 코드를 작성한다.

1
git branch 

branch 확인

branch에 대해 처음 알았고 아무 조작도 가하지 않았다면 아마도 master(또는 main) branch만 있을 것이다.
그리고 현재 적용(?)된 branch 가 색이 다르며 앞에 별표(*)가 있다.

branch 추가

이제 새롭게 branch 를 만들어보자 만약 자신이 원하는 branch 의 이름이 bugfix 라면 터미널에 다음처럼 작성하면 된다.

1
2
# git branch "branchName"
git branch bugfix

별다른 반응 없이 추가가 됐을 것이다.
확인을 위해 다시 git branch 를 해보면..

branch 추가 후 확인

이렇게 새로 branch가 만들어 졌을것이다.

branch 이동

branch를 새로이 만들었으니 이젠 새로 만든 branch에서 작업을 하기위해 이동을 해야한다.
이동하는 명령어는 다음과 같다.

1
2
# git checkout "branchName"
git checkout bugfix

branch 이동 후 확인

branch를 이동했으며 bugfix 가 활성화 된 것이 보인다.

branch 삭제

필요없는 branch를 어떻게 삭제 할까?

1
2
# git branch -d "branchName"
git branch -d bugfix

branch 이동 및 삭제 후 확인

현재 활성화된 branch를 삭제하려는 경우에는 삭제되지않으니 checkout으로 다른 branch로 이동한 다음 삭제를 시켜줘야 한다.

이렇게 branch 의 기본적인 명령어들을 알아보았는데 클론을 만들고 클론이 잘못될 경우 다시 되돌릴수 있다는 것 자체가 매우 필요한 기능이라 자주 사용하게 될듯 하다.

파이썬 데코레이터 (Python Decorator)

데코레이터란?


파이썬으로된 소스코드들을 보면, 가끔 다음과 같은 구문을 볼 수 있다.

1
2
3
@decorator
def func()
print("How to use Python")

본적은 있는것 같으나 어디에 어떻게 사용되는지 처음보는사람은 모를 수 있다.
데코레이터는 함수를 수정하지 않은 상태에서 추가기능을 구현할 때 사용한다.
일단 다음의 예시를 보자

1
2
3
4
5
6
7
8
def func1():
print("func1")

def func2():
print("func2")

func1()
func2()

위 두개의 함수에 각각 시작부분과 끝부분을 표기하고싶다면 아래와 같이 함수 시작, 끝부분에 print를 따로 넣어주어야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
def func1():
print("func1 start")
print("func1")
print("func1 end")

def func2():
print("func2 start")
print("func2")
print("func2 end")

func1()
func2()

함수가 한개, 두개라면 부담이 되지않겠지만 만약 10개, 100개, 1000개의 함수가 있고 그것을 수정해야한다면 여간 귀찮은 일이 아닐것이다.

이런 경우에 데코레이터를 사용하면 편리하다.
바로 다음 예시를 보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def dec(func):
def wrapper(func):
print(func.__name__, "start")
func()
print(func.__name__, "end")
return wrapper

def func1():
print("func1")

def func2():
print("func2")

dec_func1 = dec(func1)
dec_func1()
dec_func2 = dec(func2)
dec_func2()

'''
또는
dec(func1)()
dec(func2)()
'''
1
2
3
4
5
6
7
<Output>
func1 start
func1
func1 end
func2 start
func2
func2 end

위처럼 입력하게 될 경우 먼저 만들어졌던 dec 함수에 의해 func1, func2의 함수 출력부분에 시작과 끝을 나타내는 print가 같이 출력되게 할 수 있다.

위 코드에서 start 와 end 앞에 있는 func1,func2의 경우에는 함수의 이름이 출력되는것이고, 그 사이에 있는 func1,func2은 함수의 print 열이 출력된 것이다.

@가 있는 데코레이터 사용하기


그런데 처음에 예시로 보았던 @로 시작하는 데코레이터는 어디에도 보이지 않는다.
@를 사용하는 데코레이터는 어떻게 만드는 것일까?

@를 사용하는 데코레이터는 다음과 같이 작성한다.

1
2
3
@dec
def func1():
print("func1")

간결하게 적어서 이해가 어려울 수 있으니 이전 챕터의 가장 뒷부분 코드를 가져와 수정해보면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def dec(func):
def wrapper():
print(func.__name__, "start")
func()
print(func.__name__, "end")
return wrapper

@dec
def func1():
print("func1")

@dec
def func2():
print("func2")

func1()
func2()

결과는 같지만 무엇보다 출력부분이 매우 간결해졌다.

만약 한개의 함수에 데코레이터 여러개를 사용해야한다면 다음과 같이 할 수 있다.

1
2
3
4
5
@dec1
@dec2
@dec3
def func1():
print("func1")

코드가 위와 같을 때 여러개의 데코레이터가 지정되며 이때 데코레이터가 실행되는 순서는 위에서 아래 순으로 실행된다.

마지막으로 데코레이터를 그림으로 표현하면 다음과 같다.

데코레이터의 실행 과정

Reference

https://bluese05.tistory.com/30

파이썬(Python) with 구문

with 구문이 무엇인가?


보통 프로그램은 파일에 접근해서 파일 내용등을 읽고 쓰고 수정하는등의 일을 수행한 뒤 다시 그 파일을 마운트 해제 하는 패턴을 따른다.
예시로 사용자가 워드파일 문서 작업을 하고 있을 때 그 파일을 열고있는 동안 파일관리자가 그 문서의 이름을 바꾼다던지 파일의 경로를 변경한다던지 같은 파일에 접근을 필요로하는 행동을 타 프로그램에서 할 수 없게 된다.

결론만 말하자면 파일에 접근했으면(열었으면) 해제하는(닫아주는) 일을 빼먹지 않고 해주어야 한다는 것이다.
보통 close() 같은 메소드를 사용하여 파일을 닫아주지만 이는 문제점이 있다.
파일 처리를 수행하는 도중에 오류가 발생하게되면 아래에있는 close() 문을 실행할 수 없고 파일을 닫을수 없게된다.

with 문은 그 구문을 실행했을 때 오류가 발생하던 하지않던 마지막에 close 를 해주도록 하는 것이다.

with문 사용 예시


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 일반적인 코드
file = open('textfile.txt', 'r')
contents = file.read()
file.close()

# try finally를 사용한 코드
try:
file = open('textfile.txt', 'r')
contents = file.read()
finally:
file.close()

# with 구문을 사용한 코드
with open('textfile.txt', 'r') as file:
contents = file.read()

위의 세가지 코드는 모두 같은 내용이지만 첫번째 코드를 실행할때 contents = file.read() 행에서 에러가 난다면 file.close()를 실행하지 못해 파일을 닫을 수 없다.
그러나 아래의 두가지 코드는 에러가 발생해도 코드를 닫을 수 있을 뿐더러 with문은 더욱 간결하여 보기 편하다.



여러개의 파일 관리하기


두 개 이상의 파일을 동시에 사용할 때 with as 문을 사용하는 방법이다.
두개의 with as 문을 겹쳐도 되고, 하나의 with문에 두 개 이상의 파일을 열어도 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
with open('textfile_a.txt', 'r') as a:
with open('textfile_b.txt', 'w') as b:
a_file = a
b_file = b.write('hello wolrd')

with open('textfile_a.txt', 'r') as a, open('textfile_b.txt', 'w') as b:
a_file = a
b_file = b.write('hello world')

# 파이썬 3.10.0 b1 버전부터는 아래처럼도 가능하다
with (
open('textfile_a','w') as a,
open('textfile_b','w') as b,
open('textfile_c','w') as c
):
a.write('apple')
b.write('banana')
c.write('count')

class로 context manager 구현하기


1
2
3
4
5
6
7
class File(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value, trace_back):
self.file_obj.close()

위와 같이 정의를 하고 아래처럼 실행해 보면

1
2
with File('demo.txt', 'wb') as opened_file:
opened_file.write('Hola!')
  1. with 문은 File class의 __exit__메소드를 저장
  2. File class의 __enter__메소드를 호출
  3. __enter__메소드는 파일을 열고 파일을 반환
  4. 열려진 파일은 변수명 opened_file에 저장
  5. .write()을 통해 내용 작성
  6. with문 이기 때문에 __exit__문을 호출
  7. __exit__문을 통해 파일 닫음

위와같은 실행순서로 context manager를 구현 할 수 있다.

References.

https://tempdev.tistory.com/22
https://ddanggle.gitbooks.io/interpy-kr/content/ch24-context-manager.html

Github Repository생성 및 로컬 저장소 연결, 업로드 명령어

Github repository 생성


Github repository 생성
로그인된 깃허브 홈페이지에서 Your repositories > 우상단 New 버튼을 클릭한 후 Repository name을 입력하여 리포지토리를 생성한다.

Repository와 로컬 저장소를 연결


폴더 생성 및 터미널 실행
Repository와 같은 이름으로 폴더를 하나 생성해 준 후 폴더 아무곳이나 우클릭하여 Git Bash Here로 터미널을 실행해준다.


1
$ cd [Directory]

디렉토리 주소를 입력하면 바탕화면에서 터미널을 실행하여 명령어로 진입 할 수도 있다.


1
$ git init

명령어를 입력해 업로드할 폴더에서 깃을 init한다.(로컬 저장소 생성)

생성한 Repository의 주소 복사하기
우측의 copy버튼을 클릭하여 주소를 복사한다.


1
$ git remote add origin [Repository URL]

명령어를 입력해준 후 위에서 복사했던 주소를 붙여넣어준다. (Git Bash에서의 붙여넣기는 Shift + Insert)

Repository URL을 잘못입력했다면 아래 명령어들을 참고하면 된다.

  • 현재 로컬 저장소의 Repository URL 확인 : $ git remote -v
  • 현재 로컬 저장소의 Repository URL 변경 : $ git remote set-url origin [재설정할 Repository URL]
  • 현재 로컬 저장소의 Repository URL 삭제 : $ git remote remove origin

로컬 저장소에 있는 파일을 업로드

로컬 저장소에 있는 파일을 깃허브에 업로드하기 위해서는 다음 세가지의 명령어를 차례로 입력해주어야 한다.


1
2
$ git add .
$ git add file.py

위처럼 add 명령어로 commit 될 대상에 파일을 포함시킬수 있다.
add . 은 변동사항이 있는 모든파일을, 두번째줄은 해당파일만 포함시킨다.


1
$ git commit -m "표시할 메세지"

파일을 커밋하고 그 커밋에 대해 간단한 메세지를 남기는 명령어이다.


1
2
$ git push
$ git push origin HEAD:master

커밋한 파일들을 깃허브로 올려주는 명령어이다.
첫째 줄 명령어로 푸시가 안된다면 아래 명령어를 입력해보자

로컬 저장소와 리포지토리가 잘 연결되어 커밋된 모습

성공적으로 연결되어 커밋, 푸시까지 된 모습이다.

plotly를 사용하여 막대그래프 만들기

캐글 대회에 참가하면서 파이썬 문법이나 plotly의 특성에 대해 구글링하는 시간이 훨씬 늘었다.
앞으로도 대회에 참가하거나 시각화를 할 때에 이렇게나 많은 특성을 모두 외울수는 없으니 구글링을 하게 될 텐데, 자주쓰는 속성이나 기본틀에 대해서는 포스팅을 해두고 바로바로 찾아보는것이 좋겠다는 생각이 들었다.

그래서 이번 포스팅에서는 앞으로 자주 사용하게 될 차트 중 하나인 막대그래프의 기본 틀과 자주 사용되는 속성에 대해 포스팅 하려 한다.

데이터는 2021 Kaggle Machine Learning & Data Science Survey 대회의 데이터를 사용한다.

import 및 데이터 불러오기

1
2
3
4
5
import plotly.graph_objects as go
import pandas as pd
from plotly.subplots import make_subplots

df21 = pd.read_csv('../input/kaggle-survey-2021/kaggle_survey_2021_responses.csv')

필요한 라이브러리를 import 해주고 데이터를 불러와 저장해준다.
필자는 캐글노트북에서 작성하여 data add 기능으로 kaggle에 있는 데이터를 바로 불러왔지만 로컬이나 colab, jupyter등의 다른 노트북을 사용중이라면 데이터를 다운받은후 괄호안의 경로를 재설정해주어야 한다.

출력할 데이터 확인

1
df21[0:5][Q1,Q14] # Q1,Q14 항목의 값 0부터 4까지 출력
Q1 Q3
0 What is your age (# years)? In which country do you currently reside?
1 50-54 India
2 50-54 Indonesia
3 22-24 Pakistan
4 45-49 Mexico

이렇게 출력하거나

1
df21[df21['Q3'] == "South Korea"] # Q3 항목의 값이 "South Korea"인 행의 데이터프레임 출력

조건을 지정해 데이터를 출력해냄
위 코드처럼 어느 조건에 해당하는행을 출력 할 수도 있다.

1
2
KR_Age = df21[df21['Q3'] == 'South Korea']['Q1'].value_counts()
JP_Age = df21[df21['Q3'] == 'Japan']['Q1'].value_counts()

위 코드는 Q3(국가)이 South Korea인 행과 Japan인 행의 Q1(연령)값을 뽑아내는 코드로 이를 통해 그래프를 만들어 볼 것이다.

다중 차트 틀 만들기

위에서 import한 make_subplots으로 차트여러개를 출력 할 수 있다.

1
fig = make_subplots(rows=1, cols=2, specs=[[{'type':'xy'}, {'type':'xy'}]])

fig 객체를 생성해주고 차트를 1행, 2열로 만들어주는 코드이다.
막대그래프의 경우엔 'type':'xy' 파이그래프의 경우엔 'type':'domain'으로 할수있다.
specs에서 중괄호는 차트한개를 나타내며 내부의 중괄호가 여러개라면 각각 행을 구분하는 용도로 쓰인다.

각 속성으로 차트 그리기

1
2
3
4
5
6
7
8
9
10
11
fig.add_trace(go.Bar(name='Korea', x=KR_Age.index, y=KR_Age.values, marker_color='red'),1,1)
fig.add_trace(go.Bar(name='Japan', x=JP_Age.index, y=JP_Age.values, marker_color='blue'),1,2)

fig.update_layout(barmode='group', title_text='2021, Korea and Japan age distribution', showlegend=True)

fig.update_xaxes(title_text='Korea Age distribution', row=1, col=1)
fig.update_yaxes(title_text='Counts', row=1, col=1)
fig.update_xaxes(title_text='Japan Age distribution', row=1, col=2)
fig.update_yaxes(title_text='Counts', row=1, col=2)

fig.show()

파라미터값을 바꾸어 그래프를 출력
add_trace, update_layout 등의 파라미터(속성)값으로 그래프를 꾸미는 코드이다.


fig.add_trace(go.Bar(name='Korea', x=KR_Age.index, y=KR_Age.values, marker_color='red'),1,1)부터 살펴보면
go.Bar는 막대그래프를 뜻한다, 각 파라미터를 살펴보면
name는 그래프에 표현되는 항목의 이름,
x는 그래프의 x축이 나타낼 항목,
y는 그래프의 y축이 나타낼 값,
marker_color은 그래프의 색을 표현하며
가장 끝의1,1는 위 항목이 표시될 그래프의 행,열을 뜻한다.(그러니 1번째 행 1번째 열의 그래프에 Korea 항목이 들어간다.)


fig.update_layout(barmode='group', title_text='2021, Korea and Japan age distribution', showlegend=True)도 뜯어보겠다.
barmode는 한 그래프에 여러 항목이 들어있을 경우 어떻게 나타내는지에 대한 속성인데 대표적으로 groupstack이 있다.
title_text는 제목, showlegend는 범례 표기 여부를 가리킨다.


마지막의 fig.show()는 fig객체를 출력하는 코드이다.

아래 References에 막대그래프의 파라미터가 정리되있는 링크를 걸어놓을테니 활용하여 다양하게 그래프를 만들어보자.

References

2021 캐글 대회 코드 작성기 - 2

Q3. In which country do you currently reside?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
years = ['2019', '2021']
JP_country_count_19 = (df19[df19['Q3'] == 'Japan']['Q3']).count()
CN_country_count_19 = (df19[df19['Q3'] == 'China']['Q3']).count()
JP_country_count_21 = (df21[df21['Q3'] == 'Japan']['Q3']).count()
CN_country_count_21 = (df21[df21['Q3'] == 'China']['Q3']).count()

JP_country_count_19_21 = [JP_country_count_19, JP_country_count_21]
CN_country_count_19_21 = [CN_country_count_19, CN_country_count_21]

fig_country = go.Figure(data=[
go.Bar(name='Japan', x=years, y=JP_country_count_19_21, marker_color=JP_colors[0]),
go.Bar(name='China', x=years, y=CN_country_count_19_21, marker_color=CN_colors[1])
])
fig_country.update_layout(
barmode='group',
title_text='2019 & 2021, the number of Kaggler living in Japan and China',
xaxis_title='Years',
yaxis_title='Counts'
)
fig_country.show()

연도별 일본과 중국의 캐글러 수

2019년과 2021년 일본과 중국 캐글러의 수를 비교한 막대그래프이다.
일본은 빨강 중국은 노란색 막대로 표현했으며 일본의 수치가 인구 대비 상당히 높은 걸 알 수 있다.
양국 모두 캐글러의 수치가 증가했다.
2년간 일본은 약 35%, 중국은 약 40%가 증가했으며 증가한 캐글러의 수는 일본이 높지만 비율은 중국이 앞섰다.

Q14. What data visualization libraries or tools do you use on a regular basis?


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
df19_JP = df19[df19.Q3.isin(['Japan'])]
df19_CN = df19[df19.Q3.isin(['China'])]
df21_JP = df21[df21.Q3.isin(['Japan'])]
df21_CN = df21[df21.Q3.isin(['China'])]
df19_JP_Q14 = pd.DataFrame()
df19_CN_Q14 = pd.DataFrame()
df21_JP_Q14 = pd.DataFrame()
df21_CN_Q14 = pd.DataFrame()
df19_JP_Q14['Q20'] = [df19_JP[col][1:].value_counts().index[0] for col in df19_JP.columns[97:109]]
df19_CN_Q14['Q20'] = [df19_CN[col][1:].value_counts().index[0] for col in df19_CN.columns[97:109]]
df21_JP_Q14['Q14'] = [df21_JP[col][1:].value_counts().index[0] for col in df21_JP.columns[59:71]]
df21_CN_Q14['Q14'] = [df21_CN[col][1:].value_counts().index[0] for col in df21_CN.columns[59:71]]
df19_JP_Q14['counts'] = [df19_JP[col][1:].value_counts().values[0] for col in df19_JP.columns[97:109]]
df19_CN_Q14['counts'] = [df19_CN[col][1:].value_counts().values[0] for col in df19_CN.columns[97:109]]
df21_JP_Q14['counts'] = [df21_JP[col][1:].value_counts().values[0] for col in df21_JP.columns[59:71]]
df21_CN_Q14['counts'] = [df21_CN[col][1:].value_counts().values[0] for col in df21_CN.columns[59:71]]

df19_JP_Q14.index = [3,0,6,4,5,2,7,1,8,9,10,11]
df19_CN_Q14.index = [3,0,6,4,5,2,7,1,8,9,10,11]
df19_JP_Q14 = df19_JP_Q14.sort_index()
df19_CN_Q14 = df19_CN_Q14.sort_index()
df21_JP_Q14['Q14'].index = [0,1,2,3,4,5,6,7,8,9,10,11]
df21_CN_Q14['Q14'].index = [0,1,2,3,4,5,6,7,8,9,10,11]
df19_JP_Q14.replace(regex = 'D3.js', value = 'D3 js', inplace = True)
df19_CN_Q14.replace(regex = 'D3.js', value = 'D3 js', inplace = True)

fig_T = make_subplots(rows=1, cols=2, specs=[[{'type':'xy'}, {'type':'xy'}]])

fig_T.add_trace(go.Bar(name=coun_years[0], x=df19_JP_Q14['Q20'].values, y=df19_JP_Q14['counts'], marker_color=coun_years_colors[0]),1,1)
fig_T.add_trace(go.Bar(name=coun_years[1], x=df19_CN_Q14['Q20'].values, y=df19_CN_Q14['counts'], marker_color=coun_years_colors[1]),1,1)
fig_T.add_trace(go.Bar(name=coun_years[2], x=df21_JP_Q14['Q14'].values, y=df21_JP_Q14['counts'], marker_color=coun_years_colors[2]),1,2)
fig_T.add_trace(go.Bar(name=coun_years[3], x=df21_CN_Q14['Q14'].values, y=df21_CN_Q14['counts'], marker_color=coun_years_colors[3]),1,2)

fig_T.update_layout(title_text='2019 & 2021, Visualization Library and Tools in Use',
showlegend=True,
autosize=True)

fig_T.update_xaxes(title_text='2019 Library and Tools', row=1, col=1)
fig_T.update_yaxes(title_test='Counts', row=1, col=1)
fig_T.update_xaxes(title_text='2021 Library and Tools', row=1, col=2)
fig_T.update_yaxes(title_text='Counts', row=1, col=2)

fig_T.show()

연도별 일본과 중국의 시각화 라이브러리 & 툴 사용 그래프

각 그래프를 연도로 나누고 그래프에는 그에 해당하는 라이브러리,툴 사용량을 국가별로 나누어 넣은 막대그래프이다.
양국 모두 전반적으로 증가하였으나 눈에띄는 부분은 중국의 None항목이 크게 늘었고, 중국은 모든 항목의 사용량이 증가한 반면에 일본은 감소세가 보이는 항목이 있었다.

Q16. Which of the following machine learning frameworks do you use on a regular basis?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fig_F = make_subplots(rows=1, cols=2, specs=[[{'type':'xy'}, {'type':'xy'}]])

fig_F.add_trace(go.Bar(name=coun_years[0], x=df19_JP_Q16['Q28'].values, y=df19_JP_Q16['counts'].sort_values(ascending=False).values, marker_color=coun_years_colors[0]),1,1)
fig_F.add_trace(go.Bar(name=coun_years[1], x=df19_CN_Q16['Q28'].values, y=df19_CN_Q16['counts'].sort_values(ascending=False).values, marker_color=coun_years_colors[1]),1,1)
fig_F.add_trace(go.Bar(name=coun_years[2], x=df21_JP_Q16['Q16'].values, y=df21_JP_Q16['counts'].sort_values(ascending=False).values, marker_color=coun_years_colors[2]),1,2)
fig_F.add_trace(go.Bar(name=coun_years[3], x=df21_CN_Q16['Q16'].values, y=df21_CN_Q16['counts'].sort_values(ascending=False).values, marker_color=coun_years_colors[3]),1,2)

fig_F.update_layout(title_text='2019 & 2021, Machine Learning Frameworks in Use',
showlegend=True,
autosize=True)

fig_F.update_xaxes(title_text='2019 Machine Learning Frameworks', row=1, col=1)
fig_F.update_yaxes(title_text='Counts', row=1, col=1)
fig_F.update_xaxes(title_text='2021 Machine Learning Frameworks', row=1, col=2)
fig_F.update_yaxes(title_text='Counts', row=1, col=2)

fig_F.show()

연도별 일본과 중국의 머신러닝 프레임워크 사용 그래프

이번 질문에대한 답도 비슷하다. 그래프는 연도로 나누고 각각의 x축에는 사용하는 머신러닝 프레임워크, y축에는 그 수가 표기되어 얼마나 많은 캐글러가 머신러닝 프레임워크를 사용하지는 나타낸다.
각 년도의 차이점은 (프레임워크의 사용자의 수는 물론이거니와)프레임워크의 종류가 19년도에 비해 21년에 약증했다는 점이 있고, 특이점이라면 21년 일본의 사이킷런Scikit-learn 사용자의 수가 급증했다는 점이 있다.