library(readxl)
library(psych)
library(lattice)
library(ggplot2)
##
## Attaching package: 'ggplot2'
## The following objects are masked from 'package:psych':
##
## %+%, alpha
library(showtext)
## Loading required package: sysfonts
## Loading required package: showtextdb
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(forcats)
library(scales)
##
## Attaching package: 'scales'
## The following objects are masked from 'package:psych':
##
## alpha, rescale
library(tidyr)
library(stringr)
#install.packages("forcats")
#install.packages("showtext")
#install.packages("sysfonts")
#install.packages("curl")
#install.packages("scales")
#install.packages("tidyr")
#install.packages("stringr")
library(showtext)
library(sysfonts)
font_add_google("Noto Sans KR", "Noto Sans KR")
showtext_auto()
df <- read_excel("Austin_Animal_Center_Outcomes_(2020~2025).xlsx")
#View(df)
C375123 정유진_Big Data 502분반_분석보고서
2025.06.12.
프로젝트 주제 유기동물 입양 결정 요인에 대한 통합 분석: 건강, 연령, 보호 상태, 시계열 구조 중심으로
데이터 분석의 목적
*3의 가공 후 주요 변수에 대하여 전처리를 진행하며, 다음과 같다.
본 데이터는 동물 보호소 사건 데이터를 기반으로 하며, 총 10만여 건의 사건 기록을 포함한다. 각 행은 한 개체의 구조 및 이후 조치를 의미한다. 전처리 과정에서는 날짜 포맷 통일, 계절/연령 범주 생성, 품종/색상 정제, 입양 여부 이진화를 수행하였다.
영문 → 한글 변환 작업
df <- df %>%
mutate(
species_kr = recode(species_8,
"Dog" = "개", "Cat" = "고양이", "Bird"="새", "Livestock"="가축", "Other" = "기타"),
sex_kr = recode(sex_9,
"Neutered Male" = "중성화 수컷","Spayed Female" = "중성화 암컷", "Intact Male" = "비중성화 수컷", "Intact Female" = "비중성화 암컷","Unknown" = "미상"),
neuter_status = case_when(
grepl("Spayed|Neutered", sex_9) ~ "중성화", grepl("Intact", sex_9) ~ "비중성화",
TRUE ~ "미상"
),
neuter_status_kr = factor(neuter_status,levels = c("중성화", "비중성화", "미상")),
subtype_kr = recode(subtype_7,
"Snr" = "노령","Suffering" = "고통", "At Vet" = "병원 치료 중", "Rabies Risk" = "광견병 위험", "Foster" = "임시보호", "Partner" = "제휴기관","In Kennel" = "보호소 내","Field" = "현장 구조","Out State" = "타주 전송","Enroute" = "이송 중","In Surgery" = "수술 중", "In Foster" = "임보 중", "Medical" = "의료 필요" ,"Quarantine" = "격리 중", "Behavior" = "행동 문제", "Kennel" = "보호소 케널", "Pre-Adopt" = "입양 전 절차", "Offsite" = "외부 시설", "Cit Support" = "시민 지원", "Med Behavior" = "의료/행동 문제", "Barn" = "외양간 임시보호", "Underage" = "미성년 개체", "Neonatal" = "신생아", "Owner Surrender" = "주인 포기", "Awaiting Surgery" = "수술 대기 중", "Foster Hospice" = "호스피스 임보", "Nursing" = "수유 중",
.default = "기타"),
type_kr = recode(type_6,
"Adoption" = "입양","Transfer" = "이송","Return to Owner" = "주인 반환","Euthanasia" = "안락사","Died" = "사망",
.default = "기타"),
adopted = ifelse(type_kr == "입양", "입양", "비입양"),
color_kr = case_when(
grepl("Black", color_12, ignore.case = TRUE) ~ "검정",
grepl("White", color_12, ignore.case = TRUE) ~ "하양",
grepl("Brown", color_12, ignore.case = TRUE) ~ "갈색",
grepl("Tan", color_12, ignore.case = TRUE) ~ "탄색",
grepl("Orange", color_12, ignore.case = TRUE) ~ "주황",
grepl("Blue", color_12, ignore.case = TRUE) ~ "회색",
grepl("Gray|Grey", color_12, ignore.case = TRUE) ~ "회색",
grepl("Tabby", color_12, ignore.case = TRUE) ~ "줄무늬",
grepl("Tricolor|Tri", color_12, ignore.case = TRUE) ~ "삼색",
grepl("Brindle", color_12, ignore.case = TRUE) ~ "브린들",
grepl("Calico", color_12, ignore.case = TRUE) ~ "캘리코",
grepl("Cream", color_12, ignore.case = TRUE) ~ "크림",
grepl("Gold", color_12, ignore.case = TRUE) ~ "금색",
grepl("Chocolate", color_12, ignore.case = TRUE) ~ "초코",
grepl("Red", color_12, ignore.case = TRUE) ~ "붉은색",
grepl("Fawn", color_12, ignore.case = TRUE) ~ "베이지",
TRUE ~ "기타"
),
breed_kr = case_when(
grepl("Domestic Shorthair", breed_11, ignore.case = TRUE) ~ "국내 단모종 고양이",
grepl("Domestic Medium Hair", breed_11, ignore.case = TRUE) ~ "국내 중모종 고양이",
grepl("Domestic Longhair", breed_11, ignore.case = TRUE) ~ "국내 장모종 고양이",
grepl("Pit Bull", breed_11, ignore.case = TRUE) ~ "핏불",
grepl("Chihuahua", breed_11, ignore.case = TRUE) ~ "치와와",
grepl("Labrador", breed_11, ignore.case = TRUE) ~ "래브라도 리트리버",
grepl("German Shepherd", breed_11, ignore.case = TRUE) ~ "저먼 셰퍼드",
grepl("Dachshund", breed_11, ignore.case = TRUE) ~ "닥스훈트",
grepl("Australian Cattle Dog", breed_11, ignore.case = TRUE) ~ "오스트레일리안 캐틀 독",
grepl("Boxer", breed_11, ignore.case = TRUE) ~ "복서",
grepl("Beagle", breed_11, ignore.case = TRUE) ~ "비글",
grepl("Siberian Husky", breed_11, ignore.case = TRUE) ~ "시베리안 허스키",
grepl("Tabby", breed_11, ignore.case = TRUE) ~ "줄무늬 고양이",
grepl("Tuxedo", breed_11, ignore.case = TRUE) ~ "턱시도 고양이",
grepl("Calico", breed_11, ignore.case = TRUE) ~ "캘리코 고양이",
grepl("Mix|Mixed|/|&", breed_11, ignore.case = TRUE) ~ "혼합 품종",
grepl("Bat", breed_11, ignore.case = TRUE) ~ "박쥐",
TRUE ~ "기타"
),
age_10_kr = case_when(
!is.na(age_10) & str_detect(age_10, "year") ~ str_replace(age_10, " year[s]*", "살"),
!is.na(age_10) & str_detect(age_10, "month") ~ str_replace(age_10, " month[s]*", "개월"),
!is.na(age_10) & str_detect(age_10, "week") ~ str_replace(age_10, " week[s]*", "주"),
!is.na(age_10) & str_detect(age_10, "day") ~ str_replace(age_10, " day[s]*", "일"),
!is.na(age_10) & str_detect(age_10, "-\\d+") ~ paste0(str_replace(str_extract(age_10, "-\\d+"), "-", ""), "살 미만"),
TRUE ~ "미상"
),
subtype_kr = ifelse(is.na(subtype_kr), "미상", subtype_kr)
)
월별 → 계절로 변환
get_season <- function(month) {
if (month %in% 3:5) {
return("봄")
} else if (month %in% 6:8) {
return("여름")
} else if (month %in% 9:11) {
return("가을")
} else {
return("겨울")
}
}
df <- df %>%
mutate(
date = as.Date(substr(outcome_4, 1, 10)),
month = as.integer(format(date, "%m")),
season = sapply(month, get_season),
adopted = ifelse(type_6 == "Adoption", "입양", "비입양")
)
월별 → 나이 범주로 변환
df <- df %>%
mutate(
age_years = case_when(
str_detect(age_10, "year") ~ as.numeric(str_extract(age_10, "\\d+")),
str_detect(age_10, "month") ~ as.numeric(str_extract(age_10, "\\d+")) / 12,
str_detect(age_10, "week") ~ as.numeric(str_extract(age_10, "\\d+")) / 52,
str_detect(age_10, "day") ~ as.numeric(str_extract(age_10, "\\d+")) / 365,
TRUE ~ NA_real_
),
age_group = cut(age_years,
breaks = c(-Inf, 1, 3, 6, 10, Inf),
labels = c("1살 미만", "1~3살", "4~6살", "7~10살", "11살 이상"),
right = FALSE)
)
outcome_4에서 날짜만 추출 연-월 추출후 -01 단위 붙여 → 월단위 날짜로 변환
df <- df %>%
mutate(
date = as.Date(substr(outcome_4, 1, 10)),
month = as.Date(paste0(format(date, "%Y-%m"), "-01"))
)
*다음은 전처리가 완료된 항목이다.
df %>%
select(
date, month, season,species_kr, sex_kr, neuter_status_kr,subtype_kr, type_kr, adopted, color_kr, breed_kr,age_10_kr, age_group
) %>%
head(10)
## # A tibble: 10 × 13
## date month season species_kr sex_kr neuter_status_kr subtype_kr
## <date> <date> <chr> <chr> <chr> <fct> <chr>
## 1 2020-01-01 2020-01-01 겨울 고양이 비중성화 암컷…… 비중성화 미상
## 2 2020-01-01 2020-01-01 겨울 기타 미상 미상 광견병 위험……
## 3 2020-01-01 2020-01-01 겨울 기타 미상 미상 광견병 위험……
## 4 2020-01-01 2020-01-01 겨울 개 중성화 암컷…… 중성화 미상
## 5 2020-01-02 2020-01-01 겨울 고양이 중성화 암컷…… 중성화 임시보호
## 6 2020-01-02 2020-01-01 겨울 고양이 중성화 수컷…… 중성화 임시보호
## 7 2020-01-02 2020-01-01 겨울 고양이 중성화 암컷…… 중성화 임시보호
## 8 2020-01-02 2020-01-01 겨울 고양이 중성화 수컷…… 중성화 임시보호
## 9 2020-01-02 2020-01-01 겨울 고양이 중성화 암컷…… 중성화 임시보호
## 10 2020-01-02 2020-01-01 겨울 고양이 비중성화 암컷…… 비중성화 노령
## # ℹ 6 more variables: type_kr <chr>, adopted <chr>, color_kr <chr>,
## # breed_kr <chr>, age_10_kr <chr>, age_group <fct>
분석은 아래 순서와 같이 전개된다.
[기본 현황 분석] 유기동물은 개, 고양이가 대부분을 차지한다. 색상과 품종은 입양률에 명확한 영향을 미친다. 코로나로 인해 유기동물 수가 급감했으며 이후에는 꾸준히 상승 후 현재는 월별 변동이 존재하나 극단적인 증감 폭은 줄어들었다.
[보호 상태 분석] 노령, 병원 치료 등 건강 이상 상태에 놓인 개체는 입양률이 급격히 낮아지는 경향을 보였다.
[연령 vs 건강] 생물학적 나이와 보호소에서의 건강 판정은 일치하지 않는 경우가 많다. 2살임에도 노령으로 분류된다. 건강 저하는 입양 기피로 이어졌고, 보호소 체류 장기화 및 비용 증가로 연결되는 구조를 형성했다.
[입양 결정 요인 도출] 생후 2개월의 중성화 개체의 입양률은 85%에 달하여 매우 높은 수준을 기록했다. 중성화 여부는 입양에 긍정적인 영향을 미치는 변수로 작용하였다. 이 외에도 밝은 색상, 인기 품종, 어떤 방식으로 구조/보호되었는지 등의 변수 역시 입양률에 유의미한 영향을 미치는 것으로 분석됐다.
[품종별 경향 분석] 가장 많이 입양이 되는 생후 2개월에서 국내 단모종 고양이가 많았고 해당 나이대에 입양되는 동물의 대부분은 중성화가된 상태이다. (중성화 여부가 긍정적인 영향을 미친다는 앞선 결과와 일치한다.)
[보호 상태와 입양률 관계 분석] 건강 이상 상태에 있는 개체들의 입양률은 거의 0%에 수렴하는 것으로 나타났다. 임시보호 중이거나 제휴기관에 보호 중인 개체는 입양 전환률이 매우 높았다.
[시계열 및 계절 요인 분석] 건강 이상 상태를 가진 개체의 비중이 증가하는 시점에는 입양률이 함께 하락하는 현상이 나타났다. 또한, 여름철에는 구조 및 입양이 집중되는 반면, 겨울철에는 전반적으로 저조한 흐름을 보였다.
→ 입양은 젊고 건강하며 중성화된 개체, 특히 2개월령(85%)에서 가장 활발하게 이루어지며, 건강 이상 상태는 연령보다 훨씬 강력한 입양 저해 요인이다.
유기동물 보호소에 유입되는 개체는 종, 성별, 색상, 나이, 건강 상태 등 다양한 조건을 갖고 있으며, 입양 가능성과 직결되는 핵심 변수로 작용한다. 본 분석은 ’입양 결정 요인 분석’이라는 최종 목표에 앞서, 먼저 보호소에 유입되는 유기동물의 기초적인 특성과 분포 현황을 파악하는 것으로 시작한다.
특히, ‘어떤 동물이 보호소에 가장 많이 들어오는가’, ’어떤 특징이 빈번한가’에 대한 탐색을 통해 분석의 출발점을 설정하며, 이는 이후의 건강·연령·입양률 분석과의 연결고리를 형성한다.
보호소에 유입되는 동물의 종 분포를 분석하여, 입양 결정의 기본 단위가 되는 개체 유형을 파악한다.
ggplot(df %>% filter(!is.na(species_kr)), aes(x = species_kr)) +
geom_bar(fill = "#b3eac7") +
labs(title = "유기동물의 종류", x = "종", y = "개체수") +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold", margin = margin(b = 10)),
axis.text = element_text(size = 10),
axis.text.x = element_text(size = 10, margin = margin(b = 0, t = 6)),
axis.text.y = element_text(margin = margin(r = 10)),
axis.title.x = element_text(margin = margin(t = 20)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
ggplot(df %>% filter(sex_kr != "미상"), aes(x = factor(sex_kr))) +
geom_bar(fill = "#b3eac7") +
labs(
title = "유기동물의 성별",
x = "성별", y = "개체수" # 오탈자 "개채수" → "개체수"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
axis.text = element_text(size = 10),
axis.text.x = element_text(size = 10, margin = margin(b = 14, t = 6)),
axis.text.y = element_text(margin = margin(r = 10)),
axis.title.x = element_text(margin = margin(r = 20)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
df$color_kr <- as.factor(df$color_kr)
color_counts <- table(df$color_kr[df$color_kr != "기타"])
top_10_colors <- head(sort(color_counts, decreasing = TRUE), 10)
color_df <- as.data.frame(top_10_colors)
colnames(color_df) <- c("color_kr", "count")
color_df$color_kr <- factor(color_df$color_kr, levels = rev(color_df$color_kr))
ggplot(color_df, aes(x = count, y = color_kr)) +
geom_col(fill = "#89deb0",width = 0.7) +
labs(
title = "유기동물 색상 분포",
subtitle = "상위 10개의 색상",
x = "개체 수", y = "색상"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(size = 10),
axis.text.y = element_text(size = 10),
axis.title.x = element_text(margin = margin(t = 15)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
top_colors <- df %>%
filter(color_kr != "기타") %>%
count(color_kr) %>%
top_n(10, n) %>%
pull(color_kr)
df_color_adopt <- df %>%
filter(color_kr %in% top_colors, !is.na(adopted)) %>%
group_by(color_kr, adopted) %>%
summarise(count = n(), .groups = "drop")
df_color_adopt$color_kr <- factor(
df_color_adopt$color_kr,
levels = df_color_adopt %>%
group_by(color_kr) %>%
summarise(total = sum(count)) %>%
arrange(desc(total)) %>%
pull(color_kr) %>%
rev()
)
ggplot(df_color_adopt, aes(x = count, y = color_kr, fill = adopted)) +
geom_col(width = 0.7) +
scale_fill_manual(values = c("입양" = "#89deb0", "비입양" = "#528F70")) +
labs(
title = "색상별 입양/비입양 분포",
subtitle = "상위 10개의 색상",
x = "개체 수", y = "색상", fill = "입양 여부"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(size = 11, margin = margin(b = 6, t = 6)),
axis.text.y = element_text(size = 10),
axis.title.x = element_text(margin = margin(t = 12)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
df$breed_kr <- as.factor(df$breed_kr)
breed_counts <- table(df$breed_kr[df$breed_kr != "기타"])
top_10_breeds <- head(sort(breed_counts, decreasing = TRUE), 10)
breed_df <- as.data.frame(top_10_breeds)
colnames(breed_df) <- c("breed_kr", "count")
breed_df$breed_kr <- factor(breed_df$breed_kr, levels = rev(breed_df$breed_kr))
ggplot(breed_df, aes(x = count, y = breed_kr)) +
geom_col(fill = "#b3eac7", width = 0.7) +
labs(
title = "유기동물 품종 분포",
subtitle = "상위 10개 품종 기준",
x = "개체 수", y = "품종"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(size = 11, margin = margin(b = 6, t = 6)),
axis.text.y = element_text(size = 9),
axis.title.x = element_text(margin = margin(t = 15)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
top_breeds <- df %>%
filter(breed_kr != "기타") %>%
count(breed_kr) %>%
top_n(10, n) %>%
pull(breed_kr)
df_breed_adopt <- df %>%
filter(breed_kr %in% top_breeds, !is.na(adopted)) %>%
group_by(breed_kr, adopted) %>%
summarise(count = n(), .groups = "drop")
df_breed_adopt$breed_kr <- factor(
df_breed_adopt$breed_kr,
levels = df_breed_adopt %>%
group_by(breed_kr) %>%
summarise(total = sum(count)) %>%
arrange(desc(total)) %>%
pull(breed_kr) %>%
rev()
)
ggplot(df_breed_adopt, aes(x = count, y = breed_kr, fill = adopted)) +
geom_col(width = 0.7) +
scale_fill_manual(values = c("입양" = "#89deb0", "비입양" = "#528F70")) +
labs(
title = "품종별 입양/비입양 분포",
subtitle = "상위 10개 품종 기준",
x = "개체 수", y = "품종", fill = "입양 여부"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(size = 11, margin = margin(b = 6, t = 6)),
axis.text.y = element_text(size = 10),
axis.title.x = element_text(margin = margin(t = 12)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
df$date <- as.Date(substr(df$outcome_4, 1, 10))
df$month <- as.Date(paste0(format(df$date, "%Y-%m"), "-01"))
monthly_counts <- aggregate(x = list(n = rep(1, nrow(df))),
by = list(month = df$month),
FUN = sum)
mean_count <- mean(monthly_counts$n, na.rm = TRUE)
ggplot(monthly_counts, aes(x = month, y = n)) +
geom_hline(yintercept = mean_count, linetype = "dashed", color = "#ffb266", linewidth = 0.6) +
geom_line(color = "#5ed399", linewidth = 1) +
geom_point(color = "#5ed399", size = 1.6) +
labs(
title = "월별 유기동물 발생 건수", subtitle = "(2020-01 ~ 2025-04)",
x = "월",
y = "유기동물 수"
) +
scale_x_date(
date_breaks = "3 months", date_labels = "%Y-%m", limits = c(as.Date("2020-01-01"), as.Date("2025-05-01"))
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(angle = 45, hjust = 1, size = 7, margin = margin(b = 10, t = 6)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
subtype_counts <- table(df$subtype_kr)
top_10_subtypes <- head(sort(subtype_counts[names(subtype_counts) != "미상"], decreasing = TRUE), 10)
top_10_df <- as.data.frame(top_10_subtypes)
colnames(top_10_df) <- c("subtype", "count")
top_10_df$subtype <- factor(top_10_df$subtype, levels = top_10_df$subtype)
ggplot(top_10_df, aes(x = subtype, y = count)) +
geom_col(fill = "#89deb0") +
labs(
title = "유기동물 상태",
subtitle = "상위 10개 기준",
x = "상태", y = "유기동물 수"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(size = 8, margin = margin(b = 10, t = 6)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
임시 보호를 제외한 전반적인 유기동물의 상태는 광견병 위험, 노령, 고통 등과 같이 건강에 문제가 있는 개체들이 상당 부분을 차지하고 있는 것으로 나타난다. → 그렇다면 해당 유기동물들은 어떤 연령대에 속할까? 앞서 전체 유기동물의 연령 분포에 대해 분석한 결과를 바탕으로 고찰해볼 수 있다.
df_age <- df %>%
filter(!is.na(age_group)) %>%
count(age_group)
ggplot(df_age, aes(x = "", y = n, fill = age_group)) +
geom_bar(stat = "identity", width = 1, color = "white") +
coord_polar("y") +
labs(
title = "나이대별 유기동물 구성 비율",
fill = "나이대"
) + scale_fill_manual(values = c(
"1살 미만" = "#265B3E", # 가장 진함
"1~3살" = "#458160",
"4~6살" = "#6FA583",
"7~10살" = "#ABD3B6",
"11살 이상" = "#dbf4e0" # 가장 연함
)) +
theme_void() +
theme(
plot.margin = margin(t = 10, r = 10, b = 0, l = 10),
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold", margin = margin(b = 6)),
legend.title = element_text(size = 11),
legend.text = element_text(size = 10)
)
다음은 대부분의 비율을 차지하는 유기동물 연령대인 1개월부터 5살까지의 건강 문제와 상관관계를 도출한다. 이 연령대의 동물들은 사회성이 좋고 입양 가능성도 높지만, 유기되는 비율도 높은 중요한 대상이기에 건강에 대한 이상이 있는지의 여부를 파악하는 것이 중요하다.
age_levels <- c(
"1개월", "2개월", "3개월", "4개월", "5개월",
"1살", "2살", "3살", "4살", "5살"
)
target_subtypes_kr <- c("고통", "노령", "병원 치료 중", "광견병 위험")
df_focus <- df %>%
filter(!is.na(age_10_kr), subtype_kr %in% target_subtypes_kr) %>%
count(age_10_kr, subtype_kr)
df_focus$age_10_kr <- factor(df_focus$age_10_kr, levels = age_levels)
df_filtered <- df_focus %>%
filter(age_10_kr %in% age_levels)
ggplot(df_filtered, aes(x = age_10_kr, y = subtype_kr, size = n)) +
geom_point(alpha = 0.75, color = "#00bb6c") +
scale_size_continuous(name = "빈도 수") +
labs(
title = "주요 건강 상태와 나이 분포", subtitle = "주요 유기동물 연령(1개월~5살)",
x = "나이", y = "건강 상태"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(size = 10, margin = margin(b = 10, t = 6)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
→ 입양이 가장 활발해야 할 연령대에서 오히려 건강 문제가 집중되고 있다면, 실제 입양 양상은 어떻게 나타나는가?
age_levels <- c("1개월", "2개월", "3개월", "4개월", "5개월",
"1살", "2살", "3살", "4살", "5살")
df_age <- df %>%
filter(!is.na(age_10_kr), !is.na(type_kr)) %>%
mutate(adopted = ifelse(type_kr == "입양", "입양", "비입양"))
age_count <- df_age %>%
count(age_10_kr, sort = TRUE)
top_ages <- head(age_count$age_10_kr, 10)
df_age_top10 <- df_age %>%
filter(age_10_kr %in% top_ages) %>%
group_by(adopted, age_10_kr) %>%
summarise(count = n(), .groups = "drop") %>%
mutate(age_10_kr = factor(age_10_kr, levels = age_levels))
ggplot(df_age_top10, aes(x = age_10_kr, y = adopted, fill = count)) +
geom_tile(color = "white") +
scale_fill_gradient(low = "#dbf4e0", high = "#006644") +
labs(
title = "나이별 입양 여부", subtitle = "주요 유기동물 연령(1개월~5살)",
x = "나이", y = "입양 여부",fill = "개체 수"
) +
theme_minimal() +
theme(
axis.title.x = element_text(margin = margin(t = 12)),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(size = 10, margin = margin(b = 10, t = 6)),
text = element_text(family = "Noto Sans KR"),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
따라서 건강 상태와 연령, 특히 노령 여부는 입양 여부에 중요한 영향을 미치는 요인이다. 특히 2년령은 건강상의 문제로 유기되는 개체가 많고, 입양률은 상대적으로 낮은 특징을 보인다.
age_levels <- c("1개월", "2개월", "3개월", "4개월", "5개월",
"1살", "2살", "3살", "4살", "5살")
df_neuter <- df %>%
filter(
!is.na(neuter_status_kr),
neuter_status_kr != "미상",
!is.na(age_10_kr),
age_10_kr %in% age_levels
)
df_top_ages <- df_neuter %>%
mutate(
age_10_kr = factor(age_10_kr, levels = age_levels),
neuter_status_kr = factor(neuter_status_kr, levels = c("중성화", "비중성화"))
)
ggplot(df_top_ages, aes(x = age_10_kr, fill = neuter_status_kr)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
scale_fill_manual(
values = c("중성화" = "#C9E7D1", "비중성화" = "#528F70"),
limits = c("중성화", "비중성화")
) +
labs(
title = "유기동물의 중성화 여부 비율", subtitle = "주요 유기동물 연령(1개월~5살)",
x = "나이", y = "비율", fill = "중성화 여부"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(size = 10, margin = margin(b = 6, t = 6)),
axis.title.x = element_text(margin = margin(t = 12)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
중성화 여부는 입양 선호도에 영향을 미치는 요인으로 나타난다. 또한 건강 이상 상태 역시 입양을 어렵게 만드는 주요 요인이며, 보호소 체류 기간의 장기화와 의료비 등의 비용 증가로 이어지는 경향이 있다. 특히 중성화 여부가 건강 상태와도 관련이 있을 수 있기 때문에, 생후 2개월령 개체를 중심으로 중성화율과 입양률의 동시 분포를 확인하였다.
→ 입양이 가장 활발한 생후 2개월은 어떤 특징이 있는가?
df_2mo <- df %>%
filter(age_10_kr == "2개월") %>%
filter(!is.na(breed_kr), !is.na(type_kr)) %>%
mutate(
adopted = ifelse(type_kr == "입양", "입양", "비입양")
)
top_breeds <- df_2mo %>%
filter(breed_kr != "기타") %>%
count(breed_kr, sort = TRUE) %>%
top_n(5, n) %>%
pull(breed_kr)
df_top_breeds <- df_2mo %>%
filter(breed_kr %in% top_breeds) %>%
mutate(
breed_kr = fct_infreq(breed_kr),
adopted = factor(adopted, levels = c("입양", "비입양"))
)
ggplot(data = df_top_breeds) +
geom_bar(aes(x = breed_kr, fill = adopted), position = "dodge") +
labs(
title = "2개월령 유기동물: 품종별 입양 여부 분포",
x = "품종", y = "개체 수", fill = "입양 여부"
) +
scale_fill_manual(values = c(
"입양" = "#C9E7D1",
"비입양" = "#528F70"
)) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold", margin = margin(b = 10)),
axis.text.x = element_text(size = 9, margin = margin(b = 10, t = 6)),
axis.text.y = element_text(size = 10, margin = margin(r = 6)),
axis.title.x = element_text(margin = margin(t = 6)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
ggplot(df_top_breeds, aes(x = breed_kr, fill = neuter_status_kr)) +
geom_bar(position = "dodge") +
facet_wrap(~adopted, ncol = 1) +
labs(
title = "2개월령 유기동물: 상위 5개 품종별 입양 여부와 중성화 상태",
x = "품종", y = "개체 수", fill = "중성화 여부"
) +
scale_fill_manual(values = c(
"중성화" = "#C9E7D1",
"비중성화" = "#528F70"
)) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold", margin = margin(b = 10)),
axis.text.x = element_text(size = 9, margin = margin(b = 10, t = 6)),
axis.text.y = element_text(size = 10, margin = margin(r = 6)),
axis.title.x = element_text(margin = margin(t = 6)),
axis.title.y = element_text(margin = margin(r = 12)),
strip.text = element_text(size = 11, face = "bold"),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
df_subtype_adopt <- df %>%
filter(type_kr == "입양", !is.na(subtype_kr))
top_subtypes <- df_subtype_adopt %>%
count(subtype_kr) %>%
top_n(4, n) %>%
pull(subtype_kr)
target_subtypes <- c("노령", "고통", "광견병 위험", "병원 치료 중")
include_subtypes <- union(top_subtypes, target_subtypes)
df_top <- df %>%
filter(subtype_kr %in% include_subtypes, subtype_kr != "미상", !is.na(adopted)) %>%
group_by(subtype_kr) %>%
summarise(
total = n(),
adopted = sum(adopted == "입양"),
adoption_rate = adopted / total,
.groups = "drop"
) %>%
arrange(desc(adoption_rate)) %>%
mutate(subtype_kr = fct_inorder(subtype_kr))
ggplot(df_top, aes(x = adoption_rate, y = subtype_kr)) +
geom_segment(aes(x = 0, xend = adoption_rate, y = subtype_kr, yend = subtype_kr),
color = "#89deb0", linewidth = 2) +
geom_point(color = "#006644", size = 4) +
scale_x_continuous(labels = scales::percent_format(accuracy = 1)) +
labs(
title = "유기동물 상태",
subtitle = "입양된 개체 기반, 건강 이상 상태 포함",
x = "입양률", y = "보호 상태"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(size = 10, margin = margin(b = 10, t = 6)),
axis.text.y = element_text(size = 10, margin = margin(r = 6)),
axis.title.y = element_text(margin = margin(r = 12)),
axis.title.x = element_text(margin = margin(t = 6)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
보호 상태에 따라 입양 전환 가능성은 극명하게 갈리며, 실제로 임시보호나 외부 제휴시설을 제외하면 대부분의 보호 유형에서 입양률은 사실상 0%에 수렴하는 수준이다. 이에 따라, 입양이 어려운 보호 상태에 놓인 개체들이 어떤 특성을 가지는지 그 원인을 파악할 필요가 있다. 즉, 입양 전환이 거의 이루어지지 않는 개체들은 어떤 조건에 처해 있는가를 중심으로 분석을 진행하고자 한다.
target_subtypes_kr <- c("고통", "노령", "병원 치료 중", "광견병 위험")
df_grouped <- df %>%
filter(!is.na(age_group), subtype_kr %in% target_subtypes_kr) %>%
group_by(subtype_kr, age_group) %>%
summarise(count = n(), .groups = "drop") %>%
mutate(
age_group = factor(age_group, levels = c("1살 미만", "1~3살", "4~6살", "7~10살", "11살 이상")),
age_code = as.numeric(age_group)
)
ggplot(df_grouped, aes(x = age_group, y = count, fill = age_code)) +
geom_col() +
facet_wrap(~ subtype_kr, scales = "free_y") +
labs(
title = "보호 상태별 연령대 구성",
subtitle = "입양이 어려운 상태에 속한 개체들의 연령대 분포",
x = "연령대", y = "유기동물 수"
) +
scale_fill_gradientn(colors = c("#C9E7D1", "#006644")) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(size = 9, margin = margin(b = 6, t = 6)),
axis.text.y = element_text(size = 9, margin = margin(l = 10, r = 6)),
axis.title.x = element_text(margin = margin(t = 8)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4),
legend.position = "none"
)
그렇다면 1세에서 3세 사이의 연령대에 해당하는 개체들이 실제로 입양되었는가에 대한 검토가 필요하다. 만약 이 연령대에서조차 입양 전환률이 낮다면, 이는 나이 때문이 아니라 다른 결정적 요인, 즉 건강 상태 때문일 가능성이 높다. 실제로도 해당 연령대 개체들 중 상당수가 노령으로 분류되거나 질병·부상 등의 이유로 입양이 이루어지지 않고 있으며, 이는 건강 문제가 입양 성패를 좌우하는 주요 변수임을 뒷받침한다.
그렇다면 이 연령대 개체들이 실제로 입양되었는가? -> 나이 때문이 아니면, 정말 건강 때문인가?
df_3way <- df %>%
filter(
subtype_kr %in% c("고통", "노령", "병원 치료 중", "광견병 위험"),
!is.na(age_group),
!is.na(adopted)
) %>%
group_by(age_group, subtype_kr, adopted) %>%
summarise(count = n(), .groups = "drop")
df_3way %>%
filter(adopted == "비입양") %>%
ggplot(aes(x = age_group, y = subtype_kr, fill = count)) +
geom_tile(color = "white") +
scale_fill_gradient(low = "#dbf4e0", high = "#006644") +
labs(
title = "입양되지 못한 개체의 연령대 분포",
subtitle = "보호 상태에 따른 연령별 입양 실패 집중도",
x = "연령대", y = "보호 상태", fill = "개체 수"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(size = 11, margin = margin(b = 6, t = 6)),
axis.text.y = element_text(size = 10),
axis.title.x = element_text(margin = margin(t = 12)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
df_adopt <- df %>%
filter(!is.na(month), !is.na(type_kr)) %>%
group_by(month, adopted) %>%
summarise(count = n(), .groups = "drop")
ggplot(df_adopt, aes(x = month, y = count, color = adopted)) +
geom_line(linewidth = 1.2) +
labs(
title = "월별 유기동물 입양 및 비입양 추이", subtitle = "(2020-01 ~ 2025-04)",
x = "월", y = "유기동물 수", color = "입양 여부"
) +
scale_color_manual(values = c(
"입양" = "#5ed399",
"비입양" = "#006644"
)) +
scale_x_date(
date_breaks = "3 months", date_labels = "%Y-%m",
limits = c(as.Date("2020-01-01"), as.Date("2025-05-01"))
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(angle = 45, hjust = 1, size = 7, margin = margin(b = 10, t = 6)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
df_age <- df %>%
filter(!is.na(month), !is.na(type_6)) %>%
mutate(adopted = ifelse(type_6 == "Adoption", "입양", "비입양")) %>%
group_by(month, adopted) %>%
summarise(count = n(), .groups = "drop") %>%
tidyr::pivot_wider(names_from = adopted, values_from = count, values_fill = 0) %>%
mutate(total = 입양 + 비입양, 입양률 = 입양 / total)
ggplot(df_age, aes(x = month, y = 입양률)) +
geom_line(color = "#5ed399", linewidth = 1.2) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
scale_x_date(date_breaks = "3 months", date_labels = "%Y-%m", limits = c(as.Date("2020-01-01"), as.Date("2025-05-01"))) +
labs(
title = "월별 입양률 변화 추이", subtitle = "(2020-01 ~ 2025-04)",
x = "월", y = "입양률"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(angle = 45, hjust = 1, size = 7, margin = margin(b = 10, t = 6)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
df_total <- df %>%
filter(!is.na(month)) %>%
group_by(month) %>%
summarise(total_count = n(), .groups = "drop")
target_subtypes_kr <- c("고통", "노령", "병원 치료 중", "광견병 위험")
df_sub <- df %>%
filter(!is.na(month), subtype_kr %in% target_subtypes_kr) %>%
group_by(month, subtype_kr) %>%
summarise(sub_count = n(), .groups = "drop")
df_ratio <- merge(df_sub, df_total, by = "month") %>%
mutate(ratio = sub_count / total_count)
ggplot(df_ratio, aes(x = month, y = ratio, color = subtype_kr)) +
geom_line(linewidth = 0.8) +
geom_point(size = 1.2) +
scale_y_continuous(labels = scales::percent_format()) +
scale_x_date(date_breaks = "3 months", date_labels = "%Y-%m", limits = c(as.Date("2020-01-01"), as.Date("2025-05-01"))) +
scale_color_manual(values = c(
"광견병 위험" = "#F39C12",
"고통" = "#D63031",
"노령" = "#6C757D",
"병원 치료 중" = "#00ACC1"
)) +
labs(
title = "건강 이상 상태의 전체 구조 대비 비중 추이",
subtitle = "(월별 %)",
x = "월", y = "비중", color = "건강 이상 상태"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(angle = 45, hjust = 1, size = 7, margin = margin(b = 10, t = 6)),
axis.title.y = element_text(margin = margin(r = 12)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4),
legend.position = "bottom"
)
→ 다만 전반적으로는 입양률과 고통 상태 간 뚜렷한 반비례 관계는 보이지 않는다. 고통 상태 자체가 입양에 있어 결정적 장애 요소라고 보기는 어렵다.
→ 입양률과의 관계 광견병 위험 비중이 급증한 시기에는 입양률이 눈에 띄게 하락하는 경향을 보인다.
df_total <- df %>%
filter(!is.na(month)) %>%
group_by(month) %>%
summarise(total = n(), .groups = "drop")
target_states <- c("고통", "광견병 위험")
df_subtypes <- df %>%
filter(!is.na(month), subtype_kr %in% target_states) %>%
group_by(month, subtype_kr) %>%
summarise(count = n(), .groups = "drop") %>%
left_join(df_total, by = "month") %>%
mutate(ratio = count / total, variable = "구조 비중") %>%
select(month, subtype_kr, variable, value = ratio)
df_adopt <- df %>%
filter(!is.na(month), !is.na(type_kr)) %>%
mutate(adopted = ifelse(type_kr == "입양", "입양", "비입양")) %>%
group_by(month, adopted) %>%
summarise(count = n(), .groups = "drop") %>%
pivot_wider(names_from = adopted, values_from = count, values_fill = 0) %>%
mutate(value = `입양` / (`입양` + `비입양`)) %>%
select(month, value)
df_adopt_expanded <- expand.grid(month = unique(df_adopt$month), subtype_kr = target_states) %>%
left_join(df_adopt, by = "month") %>%
mutate(variable = "입양률")
df_plot <- bind_rows(df_subtypes, df_adopt_expanded) %>%
mutate(key = ifelse(variable == "입양률", "입양률", paste(subtype_kr, variable, sep = ".")))
ggplot(df_plot, aes(x = month, y = value, color = key)) +
geom_line(linewidth = 1.2) +
facet_wrap(~subtype_kr, ncol = 1) +
scale_color_manual(values = c(
"고통.구조 비중" = "#D63031",
"광견병 위험.구조 비중" = "#6C757D",
"입양률" = "#5ED399"
)) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
scale_x_date(date_breaks = "3 months", date_labels = "%Y-%m") +
labs(
title = "건강 이상 구조 개체 비중과 해당 시점 입양률 추이 비교",
subtitle = "2020-01 ~ 2025-04 | 고통, 광견병 위험 대상",
x = "월", y = "비율", color = "지표"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
strip.text = element_text(face = "bold", size = 11),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11),
axis.text.x = element_text(angle = 45, hjust = 1, size = 7, margin = margin(b = 10, t = 6)),
axis.text.y = element_text(size = 9),
legend.position = "bottom",
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
target_states2 <- c("노령", "병원 치료 중")
df_subtypes2 <- df %>%
filter(!is.na(month), subtype_kr %in% target_states2) %>%
group_by(month, subtype_kr) %>%
summarise(count = n(), .groups = "drop") %>%
left_join(df_total, by = "month") %>%
mutate(ratio = count / total, variable = "구조 비중") %>%
select(month, subtype_kr, variable, value = ratio)
df_adopt_expanded2 <- expand.grid(month = unique(df_adopt$month), subtype_kr = target_states2) %>%
left_join(df_adopt, by = "month") %>%
mutate(variable = "입양률")
df_plot2 <- bind_rows(df_subtypes2, df_adopt_expanded2) %>%
mutate(key = ifelse(variable == "입양률", "입양률", paste(subtype_kr, variable, sep = ".")))
ggplot(df_plot2, aes(x = month, y = value, color = key)) +
geom_line(linewidth = 1.2) +
facet_wrap(~subtype_kr, ncol = 1) +
scale_color_manual(values = c(
"노령.구조 비중" = "#00ACC1",
"병원 치료 중.구조 비중" = "#F39C12",
"입양률" = "#5ed399"
)) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
scale_x_date(date_breaks = "3 months", date_labels = "%Y-%m") +
labs(
title = "건강 이상 구조 개체 비중과 해당 시점 입양률 추이 비교",
subtitle = "2020-01 ~ 2025-04 | 노령, 병원 치료 중 대상",
x = "월", y = "비율", color = "지표"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
strip.text = element_text(face = "bold", size = 11),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11),
axis.text.x = element_text(angle = 45, hjust = 1, size = 7, margin = margin(b = 10, t = 6)),
axis.text.y = element_text(size = 9),
legend.position = "bottom",
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
→ 종합적으로, 노령·병원치료와 같은 만성적/불확실한 상태는 입양률에 부정적인 영향을 미치며, 광견병 위험은 사회적 불안 요소로 입양률 급락과 연결되는 경우가 있다.
→ 구조량 대비 입양량의 비율은 계절 간 큰 차이가 없으나, 여름철에 구조와 입양이 집중되는 경향이 특히 두드러진다. 반면 봄과 가을은 일정한 흐름을 유지하며, 겨울철은 구조 및 입양 모두 저조한 수준에 머무르는 경향이 지속된다.
df_season <- df %>%
filter(!is.na(season), !is.na(adopted)) %>%
group_by(season, adopted) %>%
summarise(count = n(), .groups = "drop")
df_season$season <- factor(df_season$season, levels = c("봄", "여름", "가을", "겨울"))
ggplot(df_season, aes(x = season, y = count, fill = adopted)) +
geom_bar(stat = "identity", position = "dodge") +
labs(
title = "계절별 유기동물 구조 및 입양 수",
subtitle = "(전체 누적 기준)",
x = "계절", y = "유기동물 수", fill = "입양 여부"
) +
scale_fill_manual(values = c(
"입양" = "#C9E7D1",
"비입양" = "#528F70"
)) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.title.y = element_text(margin = margin(r = 12)),
axis.title.x = element_text(margin = margin(t = 8)),
axis.text.x = element_text(size = 11),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
df_season_ratio <- df %>%
filter(!is.na(season), !is.na(adopted)) %>%
group_by(season, adopted) %>%
summarise(count = n(), .groups = "drop") %>%
pivot_wider(names_from = adopted, values_from = count, values_fill = 0) %>%
mutate(
total = 입양 + 비입양,
입양률 = 입양 / total
) %>%
mutate(season = factor(season, levels = c("봄", "여름", "가을", "겨울")))
ggplot(df_season_ratio, aes(x = season, y = 입양률)) +
geom_col(fill = "#b3eac7",width = 0.6) +
geom_text(aes(label = percent(입양률, accuracy = 0.1)), vjust = -0.5, size = 4) +
scale_y_continuous(labels = percent_format(accuracy = 1), limits = c(0, 1)) +
labs(
title = "계절별 입양률 비교",
subtitle = "(전체 누적 기준, 구조 대비 입양 비율)",
x = "계절", y = "입양률"
) +
theme_minimal() +
theme(
text = element_text(family = "Noto Sans KR"),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11, margin = margin(b = 10)),
axis.text.x = element_text(size = 11, margin = margin(b = 6, t = 6)),
panel.border = element_rect(color = "#505050", fill = NA, linewidth = 0.4)
)
→ 따라서 입양률 비교는 구조 수만으로는 파악되지 않는 계절별 입양 선호도의 실체를 드러낸다.
→ 구조량과 입양량은 계절, 건강 상태, 중성화 여부 등 다양한 요인의 영향을 받는다. → 종합적으로 입양 성패의 결정 요인은 건강 상태이며, 나이, 중성화, 품종, 계절 등의 변수는 그에 따라 보조적으로 작용한다.
중성화 조기 지원 정책 확대 단순한 비용 지원을 넘어, 지자체별 중성화 조기지원 전담 부서 신설 및 중성화 인증 시스템 구축이 필요하다. 또한 중성화 완료 동물에 대해 보호소-입양플랫폼 간 자동 태깅 연계를 구현함으로써, 우선 추천 대상군으로 자동 분류되도록 해야 한다. 중장기적으로는 지자체 중성화 예산 비율을 확대하고, 민간 수의사 협약 시스템을 통해 수의료 접근성을 높이는 방식으로 확장할 수 있다.
건강 중심 보호 체계 개선의 실행 체계화 모든 구조 동물에 대해 입소 48시간 이내 건강 상태 진단을 의무화하고, 건강한 개체-치료 필요 개체-예후불량 개체로 분류하는 체계를 구축해야 한다. 치료 필요 개체에 대해서는 보호소-지역 동물병원 간 협력 진료 네트워크를 확대하고, 입양 연계를 고려한 치료 후 입양 전환 트래킹 시스템을 도입하는 것이 필요하다.
임시보호 제도의 확장 및 제도화 현재 일부 보호소에서만 시범적으로 운영되는 임시보호 제도를 전국 단위로 확대 적용해야 한다. 이를 위해서는 시민 보호자 등록제, 보호자 교육 프로그램 이수 인증제, 임시보호 점수제 기반 보상 시스템 등의 제도화를 추진할 수 있다. 특히 입양으로 전환된 사례는 입양 전환 시 리워드 지급인 입양 인센티브 환급 방식으로 설계함으로써 제도 참여를 유도할 수 있다.
건강취약 개체 후원 입양 제도의 구체화 후원 입양은 보호자는 등록하되 경제적 책임은 후원자/지자체가 분담하는 구조로 정의한다. 이를 위해 후원 입양 전용 온라인 플랫폼을 구축하고, 기업 후원 연계가 가능한 시스템을 설계한다. 특히 장기 치료가 필요한 개체는 1인 1마리 후원 형태로 매칭하는 고정형 후원 모델로 발전시킬 수 있다.
계절·시계열 기반 캠페인 및 데이터 플랫폼 운영 입양률이 높은 여름철(6~8월)을 중심으로 계절 집중 캠페인을 운영한다. 또한 보호소와 입양 플랫폼이 구조-입양 트렌드 정보를 공동 공개할 수 있도록 통합 데이터 플랫폼을 마련해야 한다. 시민은 이 플랫폼을 통해 이번 달 입양률, 보호 동물 현황, 가장 입양이 시급한 개체 등의 정보를 실시간으로 확인할 수 있어야 한다. 장기적으로는 지역별 보호소 입양성과를 비교할 수 있는 입양지표 정량화 체계 도입도 고려할 수 있다.
유기동물 입양에 영향을 미치는 요인이 명확히 드러난 만큼, 관련 정보를 제공하는 시각 시스템 및 캠페인 디자인도 보다 체계적이고 직관적으로 개선할 필요가 있다. 이를 통해 시민의 행동 전환을 유도하고, 입양률을 실질적으로 높일 수 있다.
앞서 기존 유기동물 입양 어플의 장단점을 분석한다.
장점
단점
기존의 유기동물 입양 어플은 기본적인 정보 제공에는 충실하나, 입양 성공률을 높이기 위한 시각적 구조와 행동 유도 디자인 요소가 부족하다. 중성화 여부, 건강 상태, 보호 상태, 연령, 색상 등 귀하께서 도출한 입양 결정 요인들이 사용자의 선택 흐름에 반영되지 않기 때문에, 정보는 제공되나 판단 기준은 제공되지 못하고 있다. 이를 보완하기 위해 다음과 같은 디자인 전략이 필요하다.
기존 어플은 대부분 개체의 상태 정보를 텍스트 중심으로만 나열하고 있으며, 사용자는 각 조건을 해석해 스스로 판단해야 한다. → 상태 배지, 아이콘 체계를 도입한다.
기존 어플은 단순 리스트 구조로 개체 정보를 일괄적으로 나열하고 있어, 입양 가능성이 높은 개체가 묻히는 문제가 발생한다.
현재의 어플 구조는 개체를 보여주기만 할 뿐, 입양으로 이어지는 행동을 유도하는 장치가 부족하다. → 사용자의 행동을 단계별로 설계한 인터페이스가 필요하다.
건강취약 개체나 노령 동물은 입양률이 낮아 후원 또는 임시보호로 연계하는 전략이 필요하다.
기존 앱들은 이러한 시간성과 계절성을 디자인에 반영하지 못하고 있으며, 사용자는 ’지금이 왜 입양하기 좋은 시점인지’에 대한 정보 없이 앱을 사용하게 된다. → 계절별 테마 컬러와 메시지를 적용한 디자인 전략이 필요하다.