주어진 도시 데이터를 처리하여 임의의 중심값을 기준으로 가까운 도시 간 클러스터 생성 및 그래프 시각화
kmeans_pop()
, kmeans_long()
함수 구현
kmeans_pop()
kmeans_long()
class Cluster{
Point p[]
Point centerP
}
class Point{
float x
float y
}
//kmeans 함수 내부
clusters[]=newClusr()[k]
clusters = Array.from({ length: k }, () => new Cluster());
// 클러스터 및 포인트 클래스
class Cluster {
// 기본값 + 객체형으로 인수 넣을 수 있게
constructor({ centerPoint = null } = {}) {
// 좌표에 근접한 포인트 = 도시들
this.points = [];
// 해당 좌표
this.centerpoint = centerPoint;
}
}
class Point {
constructor({ x, y, name }) {
this.x = x;
this.y = y;
this.name = name;
}
}
군집을 담는 cluster변수를 Array로 선언하고, 군집은 Cluster class를 선언해 각 도시들을 담을 수 있게, 각 도시는 Point class를 구성하는 방식으로 데이터 구조를 구성했다.
K_mean 함수 호출시 입력되는 도시 데이터세트를 Point클래스를 통해 name, x, y 프로퍼티만갖는 인스턴스로 변환한다.
그룹핑된 도시목록(최단거리)이 들어갈 Points배열과, Points의 도시목록에 대한 중심좌표값을 가진다.
function NormalizeData(data) {
const xArray = data.map((point) => point.x);
const yArray = data.map((point) => point.y);
const xMax = Math.max(...xArray);
const xMin = Math.min(...xArray);
const yMax = Math.max(...yArray);
const yMin = Math.min(...yArray);
return data.map((point) => {
return {
x: (point.x - xMin) / (xMax - xMin),
y: (point.y - yMin) / (yMax - yMin),
name: point.name,
};
});
}
// 정규화 복구 함수
function DenomalizeData(data, xMax, xMin, yMax, yMin) {
return data.map((point) => {
return {
x: point.x * (xMax - xMin) + xMin,
y: point.y * (yMax - yMin) + yMin,
name: point.name,
};
});
}
Point
객체의 x
, y
좌표(년도, pop
/long
값)을 정규화하는 함수이다.function initCenterpoint(data, k) {
// 임시 중심점을 담을 배열
const centerpoint = [];
// centerpoint에 추가된 인덱스 검별용 Set
const usedIdx = new Set();
// k만큼 임시 중심점 생성
while (centerpoint.length <= k) {
// 도시 데이터를 랜덤으로 뽑을 인덱스
const randomIdx = Math.floor(Math.random() * data.length);
// 뽑은 인덱스가 아니라면 임시 중심점 추가
if (!usedIdx.has(randomIdx)) {
centerpoint.push(
new Point({
x: data[randomIdx].x,
y: data[randomIdx].y,
name: data[randomIdx].name,
}),
);
usedIdx.add(randomIdx);
}
}
return centerpoint;
}
function addClusters(data, clusters, tempCenterpoints) {
// 데이터를 순회하며 클러스터에 푸시
data.forEach((city) => {
let minDistance = 99999999;
let clusterIdx = 0;
// 길이를 대조하며, 최소 길이를 구함
tempCenterpoints.forEach((centerpoint, idx) => {
const curDistance = calculateDistance(centerpoint, city);
if (curDistance < minDistance) {
minDistance = curDistance;
clusterIdx = idx;
}
});
clusters[clusterIdx].points.push(city);
});
}
// 거리 계산 함수
function calculateDistance(point1, point2) {
// 피타고라스 정리 이용
return Math.sqrt(
Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2),
);
}
addClusters
함수를 통해 모든 도시들을 순회하면서, 모든 도시에 대해 현재 K개의 centerpoint
에 대한 거리를 구한다.(calculateDistance
함수를 통해 계산)
centerpoint
별로 거리가 최소인 도시끼리 그룹핑 시키는 함수이다. 코드상으론 아래와 같이 동작한다.
centerpoint
에 대해 Cluster객체가 생성되고, 각 cluster의 points배열에 거리가최소인 도시들이 할당된다.centerPoint
에 대해 Cluster객체의 points배열에 할당되는 식으로 동작한다.function kmeans_pop(k) {
let clusters = Array.from({ length: k }, () => new Cluster());
const parsedData = popData;
const [normalizedData, xMax, xMin, yMax, yMin] = NormalizeData(parsedData);
// 처음에 임시 중심점 초기화
let centerpoints = initCenterpoint(normalizedData, k);
let newCenterpoints = null;
// 모든 cluster의 중심점 데이터가 이전과 동일할때까지 반복
do {
// 클러스터 초기화
clusters = Array.from(
{ length: k },
(_, idx) => new Cluster({ centerpoints: centerpoints[idx] }),
);
// 가장 가까운 중심점에 속하는 그룹 생성
addClusters(normalizedData, clusters, centerpoints);
// 새로운 중심점 계산
newCenterpoints = calculateCenterpoint(clusters);
// console.log(centerpoints);
// console.log(newCenterpoints);
} while (
centerpoints.x !== newCenterpoints.x &&
centerpoints.y !== newCenterpoints.y
);
clusters.forEach((cluster) => {
cluster.points = DenomalizeData(cluster.points, xMax, xMin, yMax, yMin);
cluster.centerpoint = {
x: cluster.centerpoint.x * (xMax - xMin) + xMin,
y: cluster.centerpoint.y * (yMax - yMin) + yMin,
};
});
return clusters;
}
import { CITY_DATA } from './static.mjs';
// 클러스터 및 포인트 클래스
class Cluster {
// 기본값 + 객체형으로 인수 넣을 수 있게
constructor({ centerPoint = null } = {}) {
// 좌표에 근접한 포인트 = 도시들
this.points = [];
// 해당 좌표
this.centerpoint = centerPoint;
}
}
class Point {
constructor({ x, y, name }) {
this.x = x;
this.y = y;
this.name = name;
}
}
// 데이터 파싱
const popData = CITY_DATA.map((city) => {
const [seq, name, year, latitude, longitude, population] = city;
return new Point({ x: year, y: population, name });
});
const longData = CITY_DATA.map((city) => {
const [seq, name, year, latitude, longitude, population] = city;
return new Point({ x: year, y: longitude, name });
});
// 데이터 정규화
function NormalizeData(data) {
const xArray = data.map((point) => point.x);
const yArray = data.map((point) => point.y);
const xMax = Math.max(...xArray);
const xMin = Math.min(...xArray);
const yMax = Math.max(...yArray);
const yMin = Math.min(...yArray);
return [
data.map((point) => {
return {
x: (point.x - xMin) / (xMax - xMin),
y: (point.y - yMin) / (yMax - yMin),
name: point.name,
};
}),
xMax,
xMin,
yMax,
yMin,
];
}
// 정규화 복구 함수
function DenomalizeData(data, xMax, xMin, yMax, yMin) {
return data.map((point) => {
return {
x: point.x * (xMax - xMin) + xMin,
y: point.y * (yMax - yMin) + yMin,
name: point.name,
};
});
}
// 임시 중심점 초기화
function initCenterpoint(data, k) {
// 임시 중심점을 담을 배열
const centerpoint = [];
// centerpoint에 추가된 인덱스 검별용 Set
const usedIdx = new Set();
// k만큼 임시 중심점 생성
while (centerpoint.length < k) {
// 도시 데이터를 랜덤으로 뽑을 인덱스
const randomIdx = Math.floor(Math.random() * data.length);
// 뽑은 인덱스가 아니라면 임시 중심점 추가
if (!usedIdx.has(randomIdx)) {
centerpoint.push(
new Point({
x: data[randomIdx].x,
y: data[randomIdx].y,
name: data[randomIdx].name,
}),
);
usedIdx.add(randomIdx);
}
}
return centerpoint;
}
// 거리 계산 함수
function calculateDistance(point1, point2) {
// 피타고라스 정리 이용
return Math.sqrt(
Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2),
);
}
// 4. 가장 가까운 임시 중심값에 속하는 K개 그룹 생성
function addClusters(data, clusters, tempCenterpoints) {
// 데이터를 순회하며 클러스터에 푸시
data.forEach((city) => {
let minDistance = 99999999;
let clusterIdx = 0;
// 길이를 대조하며, 최소 길이를 구함
tempCenterpoints.forEach((centerpoint, idx) => {
const curDistance = calculateDistance(centerpoint, city);
if (curDistance < minDistance) {
minDistance = curDistance;
clusterIdx = idx;
}
});
clusters[clusterIdx].points.push(city);
});
}
// 무게중심을 이용해 중심값을 계산하는 함수
function calculateCenterpoint(clusters) {
clusters.forEach((cluster) => {
const xSum = cluster.points.reduce((acc, cur) => acc + cur.x, 0);
const ySum = cluster.points.reduce((acc, cur) => acc + cur.y, 0);
cluster.centerpoint = new Point({
x: xSum / cluster.points.length,
y: ySum / cluster.points.length,
});
});
return clusters.map((cluster) => cluster.centerpoint);
}
function kmeans_pop(k) {
let clusters = Array.from({ length: k }, () => new Cluster());
const parsedData = popData;
const [normalizedData, xMax, xMin, yMax, yMin] = NormalizeData(parsedData);
// 처음에 임시 중심점 초기화
let centerpoints = initCenterpoint(normalizedData, k);
let newCenterpoints = null;
// 모든 cluster의 중심점 데이터가 이전과 동일할때까지 반복
do {
// 클러스터 초기화
clusters = Array.from(
{ length: k },
(_, idx) => new Cluster({ centerpoints: centerpoints[idx] }),
);
// 가장 가까운 중심점에 속하는 그룹 생성
addClusters(normalizedData, clusters, centerpoints);
// 새로운 중심점 계산
newCenterpoints = calculateCenterpoint(clusters);
// console.log(centerpoints);
// console.log(newCenterpoints);
} while (
centerpoints.x !== newCenterpoints.x &&
centerpoints.y !== newCenterpoints.y
);
clusters.forEach((cluster) => {
cluster.points = DenomalizeData(cluster.points, xMax, xMin, yMax, yMin);
// console.log(cluster.centerpoint.x * (xMax - xMin), xMax, xMin);
cluster.centerpoint = {
x: Math.floor(cluster.centerpoint.x * (xMax - xMin) + xMin),
y: Math.floor(cluster.centerpoint.y * (yMax - yMin) + yMin),
};
});
return clusters;
}
function kmeans_long(k) {
let clusters = Array.from({ length: k }, () => new Cluster());
const parsedData = longData;
const [normalizedData, xMax, xMin, yMax, yMin] = NormalizeData(parsedData);
// 처음에 임시 중심점 초기화
let centerpoints = initCenterpoint(normalizedData, k);
let newCenterpoints = null;
// 모든 cluster의 중심점 데이터가 이전과 동일할때까지 반복
do {
// 클러스터 초기화
clusters = Array.from(
{ length: k },
(_, idx) => new Cluster({ centerpoints: centerpoints[idx] }),
);
// 가장 가까운 중심점에 속하는 그룹 생성
addClusters(normalizedData, clusters, centerpoints);
// 새로운 중심점 계산
newCenterpoints = calculateCenterpoint(clusters);
// console.log(centerpoints);
// console.log(newCenterpoints);
} while (
centerpoints.x !== newCenterpoints.x &&
centerpoints.y !== newCenterpoints.y
);
clusters.forEach((cluster) => {
cluster.points = DenomalizeData(cluster.points, xMax, xMin, yMax, yMin);
// console.log(cluster.centerpoint.x * (xMax - xMin), xMax, xMin);
cluster.centerpoint = {
x: (cluster.centerpoint.x * (xMax - xMin) + xMin).toFixed(2),
y: (cluster.centerpoint.y * (yMax - yMin) + yMin).toFixed(2),
};
});
return clusters;
}
const returnClusters = kmeans_long(5).map((cluster, idx) => {
return `그룹#${idx + 1} 중심값 (${cluster.centerpoint.x}, ${
cluster.centerpoint.y
})\n그룹#${idx + 1} 도시들 ${cluster.points
.map((point) => point.name)
.join(', ')}`;
});
console.log(returnClusters);