본문 바로가기

Study/class note

python / 산포도그래프, 라인그래프, 박스그래프, 상관계수그래프

seaborn의 히스토그램 그리는 distplot은 곧 사라질 메소드 > histplot으로 그리면 됨

# seaborn
import pandas as pd
import seaborn as sns

emp14 = pd.read_csv("c:\\data\\emp14.csv")
sns.histplot(emp14['age'], bins = 10)

bins는 계급의 갯수 => 막대기 몇 개 나타낼 거냐는 의미

 

ㅇx축 지정 디테일하게 하는 방법

#한글 안깨지게 하는 코드
from matplotlib import font_manager, rc 
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() 
rc('font', family=font_name)

import  pandas  as  pd
import  seaborn  as  sns
import matplotlib.pyplot as plt

emp14 = pd.read_csv("c:\\data\\emp14.csv")

fig, ax = plt.subplots()   # fig는 전체 그래프의 사이즈를 지정, ax는 축에 대한 유저설정을 위한 것
sns.set_style('darkgrid') # 스타일 설정, 스타일 설정을 넣는 위치에 따라 한글이 깨지고 안깨짐. 중요!!!!
sns.distplot( emp14.age,bins=10,color = 'red', kde = False) # 히스토그램 그래프 그리는데 계급 10개, kde = 확률밀도를 나타냄
ax.set_xlim(20,35)  # x축에 눈금 표시
ax.set_xticks(range(20,35)) # x축 눈금 아래 숫자 범위 지정

plt.title('우리반 나이 히스토그램', size = 20)
plt.show()

kde = False를 쓰게 되면 y축이 확률이 아니라 건수로 출력됨.

 

확률밀도함수 > 확률 변수의 분포를 나타내는 함수

확률변수 종류 2가지

1. 이산확률변수 : 정수와 같이 명확한 값을 변수 값으로 함 ex. 2개의 동전을 던져서 나오는 앞면의 수

2. 연속확률변수 : 변수값이 정수처럼 명확하지 않은 것. 확률변수가 연속량으로 표기되어 가능한 변수값의 갯수를 셀 수 없는 변수 ex. 서울지역 초등학교 학생들의 키

 

문제541. 위의 코드를 자동화 코드에 12번으로 추가하시오.

c = """
#한글 안깨지게 하는 코드
from matplotlib import font_manager, rc 
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() 
rc('font', family=font_name)

# 판다스
import pandas as pd
 
emp14 = pd.read_csv("c:\\data\\emp14.csv")
emp14['age'].plot(kind='hist')

# seaborn - distplot : 곧 사라질 메소드
import pandas as pd
import seaborn as sns
 
emp14 = pd.read_csv("c:\\data\\emp14.csv")
sns.distplot(emp14['age'])

# seaborn - histplot
sns.histplot(emp14['age'], bins = 10)

# x축 조정 방법
import  pandas  as  pd
import  seaborn  as  sns
import matplotlib.pyplot as plt

emp14 = pd.read_csv("c:\\data\\emp14.csv")

fig, ax = plt.subplots()   # fig는 전체 그래프의 사이즈를 지정, ax는 축에 대한 유저설정을 위한 것
sns.set_style('darkgrid') # 스타일 설정, 스타일 설정을 넣는 위치에 따라 한글이 깨지고 안깨짐. 중요!!!!
sns.distplot( emp14.age,bins=10,color = 'red', kde = False) # 히스토그램 그래프 그리는데 계급 10개, kde = 확률밀도를 나타냄
ax.set_xlim(20,35)  # x축에 눈금 표시
ax.set_xticks(range(20,35)) # x축 눈금 아래 숫자 범위 지정

plt.title('우리반 나이 히스토그램', size = 20)
plt.show()
"""

 

문제542. 중고차 가격 데이터를 히스토그램 그래프로 그리시오.

#한글 안깨지게 하는 코드
from matplotlib import font_manager, rc 
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() 
rc('font', family=font_name)

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
 
usedcars = pd.read_csv("c:\\data\\usedcars.csv", encoding = 'utf8')
# usedcars
sns.histplot(usedcars['price'], kde = True)

plt.title('중고차 가격 히스토그램', size = 20)
plt.show()

 

156 산포도 그래프 그리기

산포도 그래프 > 데이터의 퍼짐 정도를 한눈에 확인할 수 있는 그래프

데이터 퍼짐정도를 나타내는 분산을 시각화해서 확인할 수 있고 두개의 변수(컬럼) 간의 상관관계를 확인할 때 유용

 

1. 판다스

ex. 미국 국민 비만도에 따른 의료비 분포

import pandas as pd

ins = pd.read_csv("c:\\data\\insurance.csv")
ins.plot(style = ".", x = "bmi", y = "expenses")

+) 피어슨 상관관계

import pandas as pd

ins = pd.read_csv("c:\\data\\insurance.csv")
ins.corr(method = 'pearson')  # 피어슨 상관관계

2. Seaborn

- regplot

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

ins = pd.read_csv("c:\\data\\insurance.csv")
sns.set_style('darkgrid')
sns.regplot(x='bmi', y = 'expenses', data = ins, fit_reg = True)  # 회귀직선 안보고 싶으면 False
plt.show()

- stripplot : 분산은 확인할 수 없는 x축 범주형 산포도 그래프

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

tat = sns.load_dataset('titanic')   # seaborn에 내장된 타이타닉호 데이터
sns.set_style('whitegrid')
sns.stripplot(x='class', y = 'age', data = tat) # x축 class, y축 age로 산포도 그래프

first class의 승객이 다른 class보다 나이가 많은 사람이 보이고, first class의 승객 중 나이가 어린 승객이 다른 class의 승객들보다 숫자가 작다. 

 

문제543. 위의 stripplot으로 산포도 그래프를 그리는데 x축을 직업으로 하고 y축을 월급으로 하여 산포도 그래프를 그리시오.

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

emp = pd.read_csv("c:\\data\\emp2.csv")
sns.set_style('whitegrid')
sns.stripplot(x='job', y = 'sal', data = emp)

문제544. 중고차 데이터(usedcars.csv)에서 x축을 모델명으로 두고 y축을 가격으로 두고 산포도 그래프를 그리시오.

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

cars = pd.read_csv("c:\\data\\usedcars.csv")
sns.set_style('whitegrid')
sns.stripplot(x='model', y = 'price', data = cars)

- swarmplot : 분산까지 확인할 수 있는 x축 범주형 산포도 그래프

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

tat = sns.load_dataset('titanic')   # seaborn에 내장된 타이타닉호 데이터
sns.swarmplot(x='class',y='age',data=tat)

swarmplot는 분산을 고려한 산포도가 그려짐. (데이터의 퍼짐정도 확인 가능)

 

third class의 경우 20대와 30대가 주를 이루고 있고, first class에는 30대 후반의 나이대가 많아 보임.

 

문제545. 위의 그래프를 다시 그리는데 x축은 성별, y축은 나이로 해서 시각화하시오.

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

tat = sns.load_dataset('titanic')   # seaborn에 내장된 타이타닉호 데이터
sns.swarmplot(x='sex',y='age',data=tat)

문제546. 위의 산포도 그래프 코드를 정리해서 자동화 코드에 넣으시오.

d = """
## 판다스
import pandas as pd

ins = pd.read_csv("c:\\data\\insurance.csv")
ins.plot(style = ".", x = "bmi", y = "expenses")
ins.corr(method = 'pearson')  # 피어슨 상관관계

## Seaborn- regplot : 회귀 직선 추가한 그래프
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

ins = pd.read_csv("c:\\data\\insurance.csv")
sns.set_style('darkgrid')
sns.regplot(x='bmi', y = 'expenses', data = ins, fit_reg = True)  # 회귀직선 안보고 싶으면 False
plt.show()

## Seaborn- stripplot : 분산은 확인할 수 없는 x축 범주형 산포도 그래프
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

tat = sns.load_dataset('titanic')   # seaborn에 내장된 타이타닉호 데이터
sns.set_style('whitegrid')
sns.stripplot(x='class', y = 'age', data = tat) # x축 class, y축 age로 산포도 그래프

## Seaborn- swarmplot : 분산까지 확인할 수 있는 x축 범주형 산포도 그래프
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

tat = sns.load_dataset('titanic')   # seaborn에 내장된 타이타닉호 데이터
sns.swarmplot(x='class',y='age',data=tat)
"""

문제547. 중고차 데이터에서 mileage와 price의 상관관계를 구하고 산포도 그래프를 그리시오. 

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

cars = pd.read_csv("c:\\data\\usedcars.csv")

## 판다스
sns.set_style('darkgrid')
cars.plot(style = ".", x = "mileage", y = "price")
cars.corr(method = 'pearson')  # 피어슨 상관관계

문제548. (점심시간 문제) 미국 국민 의료비 데이터를 가지고 x축을 smoker, y축을 expenses로 하여 swarmplot로 산포도 그래프를 그리고 해석하시오.

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

ins = pd.read_csv("c:\\data\\insurance.csv")
sns.set_style('whitegrid')
sns.swarmplot(x='smoker', y='expenses', data = ins)

+) 상관관계

 


157 라인 그래프 그리기

선 그래프 또는 꺾은 선 그래프 라고 하는데 수량을 점으로 표시하고 그 점들을 선분으로 이어 그린 그래프. 시간에 따라서 지속적으로 변화하는 과정을 볼 때 굉장히 유용함.

 

1. 판다스

import pandas as pd
import matplotlib.pyplot as plt

dict_data = {'week' : [1,2,3,4,5], 'cars' : [1,3,6,4,9], 'truck' : [2,5,4,5,12]}
df = pd.DataFrame(dict_data)

ax = df[['cars','truck']].plot.line() # ax 안써줘도 그래프가 그려지기는 함. 아래의 눈금 그리려고 변수에 넣음

ax.set_xticks( (0,1,2,3,4))  # x축의 눈금, 파이썬은 0번부터 시작
ax.set_xticklabels( ('mon','tue','wed','thr','fri'))

문제549. 치킨집의 창업 현황을 연도별에 따라 라인그래프를 그리시오

import pandas as pd
import matplotlib.pyplot as plt

#한글 안깨지게 하는 코드
from matplotlib import font_manager, rc 
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() 
rc('font', family=font_name)

aa = pd.read_csv("c:\\data\\창업건수.csv", encoding = 'euckr')
ax = aa[['치킨집']].plot.line()
        
ax.set_xticks((0,1,2,3,4,5,6,7,8,9))
ax.set_xticklabels(tuple(range(2005,2015,1)))

문제550. 치킨집의 창업현황과 함께 커피음료의 창업현황을 라인그래프로 그리시오.

import pandas as pd
import matplotlib.pyplot as plt

#한글 안깨지게 하는 코드
from matplotlib import font_manager, rc 
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() 
rc('font', family=font_name)

aa = pd.read_csv("c:\\data\\창업건수.csv", encoding = 'euckr')
ax = aa[['치킨집','커피음료']].plot.line()
        
ax.set_xticks((0,1,2,3,4,5,6,7,8,9))
ax.set_xticklabels(tuple(range(2005,2015,1)))

문제551. 치킨집의 창업건수와 폐업 건수를 같이 선그래프로 시각화 하시오.

import pandas as pd
import matplotlib.pyplot as plt

#한글 안깨지게 하는 코드
from matplotlib import font_manager, rc 
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() 
rc('font', family=font_name)

aa = pd.read_csv("c:\\data\\창업건수.csv", encoding = 'euckr')
bb = pd.read_csv("c:\\data\\폐업건수.csv", encoding = 'euckr')

cc = pd.merge(aa, bb, on = '년도')
ax = cc[['치킨집_x','치킨집_y']].plot.line()
        
ax.set_xticks((0,1,2,3,4,5,6,7,8,9))
ax.set_xticklabels(tuple(range(2005,2015,1)))
plt.legend(labels = ['창업','폐업'])  # 범례 수정
plt.show()

 

2. seaborn

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

dict_data = {'week' : [1,2,3,4,5], 'cars' : [1,3,6,4,9], 'truck':[2,5,4,5,12]}
df = pd.DataFrame(dict_data)

sns.set(style = 'darkgrid')
ax = sns.lineplot(x='week', y = 'cars', data = df )  # 2개의 자료를 그리고 싶으면 하나 더 만들면 됨
ax = sns.lineplot(x='week', y = 'truck', data = df )

ax.set_xticks((1,2,3,4,5))
ax.set_xticklabels( ('mon','tue','wed','thr','fri'))

plt.legend(['cars','truck'])  # 범례 써줘야함
plt.show()

문제552. 창업건수.csv를 선 그래프로 시각화 하시오(seaborn)

import pandas as pd
import matplotlib.pyplot as plt

#한글 안깨지게 하는 코드
from matplotlib import font_manager, rc 
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() 
rc('font', family=font_name)

aa = pd.read_csv("c:\\data\\창업건수.csv", encoding = 'euckr')
ax = sns.lineplot(x = '년도', y = '치킨집',marker = 'o', data = aa)  # marker를 넣으면 점이 찍힘
 
# plt.xlabel('년도', labelpad = 15)  만약 라벨 바꾸고 싶을 경우 이렇게 하면 됨
# plt.ylabel('치킨집', labelpad = 20)

ㅇ 마커(marker)의 종류

문제553. 치킨집의 연도별 창업건수와 폐업건수를 선그래프로 같이 시각화 하시오.

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

aa = pd.read_csv("c:\\data\\창업건수.csv", encoding = 'euckr')
bb = pd.read_csv("c:\\data\폐업건수.csv", encoding = 'euckr')

cc = pd.merge(aa,bb, on = '년도')
ax = sns.lineplot(x = '년도', y = '치킨집_x', marker = 'o', data = cc) 
ax = sns.lineplot(x = '년도', y = '치킨집_y', marker = 's', data = cc) 

plt.rc('font',family = 'Malgun Gothic')  # 한글 안깨지게 하는 코드
plt.legend(['창업','폐업'])
plt.show()

문제554. 라인 그래프를 자동화 코드로 완성하기 위해 코드를 간추리고 자동화 코드 14번에 넣으시오.

e = """
############ 판다스 ############
import pandas as pd
import matplotlib.pyplot as plt

aa = pd.read_csv("c:\\\data\\\창업건수.csv", encoding = 'euckr')
ax = aa.plot(kind = 'line', x = '년도', y = ['치킨집', '커피음료'], marker = 'o')

plt.rc('font', family='Malgun Gothic')

# ax = aa.plot.line(x = '년도', y = ['치킨집', '커피음료'], marker = 'o')  - 판다스 방법2

# ax = aa[['치킨집','커피음료']].plot.line( marker = 'o')  - 판다스 방법3
# ax.set_xticks((0,1,2,3,4,5,6,7,8,9))   # x축에 년도 설정
# ax.set_xticklabels(tuple(range(2005,2015,1)))

############ seaborn ############
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

aa = pd.read_csv("c:\\\data\\\창업건수.csv", encoding = 'euckr')
bb = pd.read_csv("c:\\\data\\\폐업건수.csv", encoding = 'euckr')

cc = pd.merge(aa,bb, on = '년도')
ax = sns.lineplot(x = '년도', y = '치킨집_x', marker = 'o', data = cc) 
ax = sns.lineplot(x = '년도', y = '치킨집_y', marker = 's', data = cc) 

plt.rc('font',family = 'Malgun Gothic')  # 한글 안깨지게 하는 코드
plt.legend(['창업','폐업'])
plt.show()

"""

print(e)

 


158 박스 그래프 그리기

사분위수 그래프라고 하는데 이 그래프는 데이터를 그림을 이용하여 집합의 범위와 중앙값을 빠르게 확인할 수 있으며 또한 이상치 값이 있는지 빠르게 확인이 가능한 그래프임.

 

평균값, 중앙값, 최빈값만으로는 데이터 분석을 하기가 부족한 경우가 있음. 특히, 평균 데이터는 데이터의 중심이 어디쯤인지 알려주지만 특정 데이터가 평균을 중심으로 어떻게 분표 되었는지 알려주지는 않음.

그래서 필요한 게 사분위수 그래프

 

ㅇ 데이터의 분포를 확인하는 방법 3가지

1. 범위

2. 사분위수 범위

3. 히스토그램 그래프

 

1. 판다스

ex. 농구선수 3명의 경기당 득점 점수가 다음과 같을 때, 농구 감독이 이 3명 중에 한 명을 선택해야 한다면 누구를 선택하겠는가?

import pandas as pd

ball2 = {'x1':[7,8,9,9,10,10,11,11,12,13],
         'x2':[7,9,9,10,10,10,10,11,11,13],
         'x3':[1,1,7,7,10,10,10,11,13,30]}

ball = pd.DataFrame(ball2)
ball

문제555. 위의 3명의 선수들의 득점에 대한 평균값을 각각 출력하시오

import pandas as pd

ball2 = {'x1':[7,8,9,9,10,10,11,11,12,13],
         'x2':[7,9,9,10,10,10,10,11,11,13],
         'x3':[1,1,7,7,10,10,10,11,13,30]}

ball = pd.DataFrame(ball2)
ball

print(ball.x1.mean())
print(ball.x2.mean())
print(ball.x3.mean())

평균값으로는 값이 다 같아서 위의 3명의 선수 중 한 명을 선택할 수 없음. 그래서 다른 데이터 분석 방법이 필요한데 그게 바로 데이터의 분포를 확인하는 것 = > 범위, 사분위수 범위, 히스토그램 그래프

 

문제556. 위의 3명의 선수들의 점수 범위를 출력하시오.

import pandas as pd

ball2 = {'x1':[7,8,9,9,10,10,11,11,12,13],
         'x2':[7,9,9,10,10,10,10,11,11,13],
         'x3':[1,1,7,7,10,10,10,11,13,30]}

ball = pd.DataFrame(ball2)
ball

ball.quantile([0,1]) # 범위(최솟값, 최대값)

범위는 그 자체로는 데이터의 폭만 설명할 뿐 그 안에서 데이터 분포되는 방식은 설명해주지 않음. 특히 이상치에 민감함.

3번째 선수의 경우 어쩌다 한 번 잘한 게임(30점)인 이상치 때문에 범위가 넓어져 버림. 그러므로 이상치로 부터 멀어질 필요가 있음. 이상치로부터 멀어지고 가운데 있는 데이터에만 집중하게 해주는게 사분위수 범위.

 

사분위수 범위(iqr = Inter Qurtile Range) = Q3 - Q1

ball['x3'].describe()

# count    10.000000
# mean     10.000000
# std       8.096639
# min       1.000000  <-- 최소값(min)
# 25%       7.000000  <-- 최소 사분위수(0.25), Q1
# 50%      10.000000  <-- 중앙값
# 75%      10.750000  <-- 최대 사분위수(0.75), Q3
# max      30.000000  <-- 최대값

 

문제557. 1번째, 2번째 선수들의 사분위수 범위를 출력하시오

ball['x1'].describe()

# count    10.000000
# mean     10.000000
# std       1.825742
# min       7.000000
# 25%       9.000000  <-- 최소 사분위수(0.25), Q1
# 50%      10.000000
# 75%      11.000000  <-- 최대 사분위수(0.75), Q3
# max      13.000000

ball['x2'].describe()

# count    10.000000
# mean     10.000000
# std       1.563472
# min       7.000000
# 25%       9.250000  <-- 최소 사분위수(0.25), Q1
# 50%      10.000000
# 75%      10.750000  <-- 최대 사분위수(0.75), Q3
# max      13.000000

문제558. 위의 농구선수 3명의 득점 데이터를 사분위수 범위 그래프로 그리시오.

import pandas as pd

ball2 = {'x1':[7,8,9,9,10,10,11,11,12,13],
         'x2':[7,9,9,10,10,10,10,11,11,13],
         'x3':[1,1,7,7,10,10,10,11,13,30]}

ball = pd.DataFrame(ball2)

ball.plot(kind ='box')

가운데 박스 크기가 클수록 사분위수 범위가 넓음. 

1번째 선수, 2번째 선수가 3번째 선수보다 상대적으로 사분위수 범위가 좁음. 3번째 선수는 1번째, 2번째 선수에 비해 훨씬 높은 점수(30점)을 득점했지만 다른 경우에는 훨씬 낮은 점수(1점)에 대한 기록도 보임. 

따라서, 1번째, 2번째 선수의 점수가 더 일관성 있고 대부분의 경우 3번째 선수보다 더 높은 점수를 기록했음. 그래서 3명 중 한 명을 고른다면, 1번째 선수나 2번재 선수가 고려될 것.

 

2. seaborn

문제559. seaborn으로 농구 선수 3명에 대한 사분위수 그래프를 그리시오

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

ball2 = {'x1':[7,8,9,9,10,10,11,11,12,13],
         'x2':[7,9,9,10,10,10,10,11,11,13],
         'x3':[1,1,7,7,10,10,10,11,13,30]}

ball = pd.DataFrame(ball2)
ax = plt.subplots(figsize = (7,5))
sns.boxplot(data=ball)

문제560. 박스 그래프도 자동화 코드에 15번으로 추가하시오.

f = """
########## 판다스 ##########
import pandas as pd

ball2 = {'x1':[7,8,9,9,10,10,11,11,12,13],
         'x2':[7,9,9,10,10,10,10,11,11,13],
         'x3':[1,1,7,7,10,10,10,11,13,30]}
         
ball = pd.DataFrame(ball2)     
ball.plot(kind ='box')

########## seaborn ##########
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

ball2 = {'x1':[7,8,9,9,10,10,11,11,12,13],
         'x2':[7,9,9,10,10,10,10,11,11,13],
         'x3':[1,1,7,7,10,10,10,11,13,30]}

ball = pd.DataFrame(ball2)
ax = plt.subplots(figsize = (7,5))
sns.boxplot(data=ball)
"""

 

 

159 상관관계 그래프 그리기

상관관계란 두 개의 변수(컬럼) 간의 선형 관계정도를 시각화하는 그래프

 

1. seaborn

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

ins = pd.read_csv("c:\\data\\insurance.csv")
sns.heatmap(ins.corr(), annot = True, cmap = 'RdYlGn', linewidths = 0.2)  
fig = plt.gcf() # fig를 지정하고 나서
fig.set_size_inches(10,8) # 그래프의 사이즈 조절
plt.show()

annot=True는 상관계수값 나타내기, cmap은 색상 지정, linewidths 는 중간에 흰색 라인 선의 굵기

 

 

문제561. (오늘의 마지막 문제) 중고차데이터(usedcars.csv)로 상관관계 그래프를 그리시오. price와 가장 상관관계가 높은 컬럼이 무엇인지 확인하세요.

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

cars = pd.read_csv("c:\\data\\usedcars.csv")
sns.heatmap(cars.corr(), annot = True, cmap = 'GnBu', linewidths = 0.2)  
fig = plt.gcf() # fig를 지정하고 나서
fig.set_size_inches(10,8) # 그래프의 사이즈 조절
plt.show()

 

+) cmap 색상표

반응형