문자 선언

//문자 1개
char alpha = 'C';
// 문자열 1개
char id[5] = "Nabi";

 

Nabi는 4글자인데 id[5]로 5글자를 저장하겠다고 코드를 작성하면

'N' 'a' 'b' 'i' '\0'로 저장이 된다. 

 

// 문자열 3개
char fr[3][6] = {"Jaeil", 
		"Doori", 
                "Gosu"}

 

마찬가지로 행의 길이가 6글자로 저장된다고 했는데 5글자, 4글자로 문자열이 저장되어있다.

이 같은 경우에도 \0이 남는 자리에 저장이 된다. 

 

원소 호출

 

 

원소 호출을 할 때는 2차원 배열을 호출할 때 하는 것처럼 하면 된다. 

 

 

 

문자열 단위의 입출력

 

문자열의 시작주소

 

char형 1차원 배열일 때

배열명: 문자열 시작 주소

// 문자열 1개
char id[5] = "Nabi";

 

id: 문자열 시작 주소 (포인터)

 

 

 

char형 2차원 배열일 때

배열명[행첨자] : 해당 행 문자열 시작 주소

// 문자열 3개
char fr[3][6] = {"Jaeil", 
		"Doori", 
                "Gosu"}

 

- fr[0] : 1번째 문자열 시작 주소

- fr[1] : 2번째 문자열 시작 주소

- fr[2] : 3번째 문자열 시작 주소

 

 

 

기억!!

배열명은 배열 시작 주소이며 배열 시작 위치를 가리킨다!

 

 

단순 문자열을 출력하기 위한 형식 

문자열 입력

scanf("%s", 문자열 저장 시작 주소);

  • 키보드에서 입력되는 문자열을 지정한 주소의 기억장소부터 차례대로 저장함.
  • 배열에 남는 공간이 있어야 입력 문자열 뒤에 널 문자가 저장됨.
  • ex) scanf("%s", id); , scanf("%s", fr[2])  (+&가 없다!)

문자열 출력

printf("%s", 문자열이 저장된 시작 주소);

  • 시작 주소로부터 저장된 문자를 연속으로 출력하되 널 문자를 만나면 출력을 끝냄.
  • ex) printf("%s", id);, printf("%s", fr[2])

 

 

주의!

문자열 시작 주소와 %를 이용한 문자열 출력은 널 문자('\0')를 만나야 출력이 끝난다. 

다음 sur 배열은 배열 원소 수가 문자열의 길이 즉, 실제 문자 개수와 똑같다.

그러므로 널 문자가 저장되지 않기에 이상한 결과가 나올 수 있다.

 

참고로, 비주얼 스튜디오에서 위의 코드를 '*.c'와 같이 C언어 소스 파일로 저장하면 실행이 된다.

하지만 C++언어는 배열에 널 문자가 들어갈 공간이 없으면 오류가 되어 실행 자체가 되지 않는다.

 

 

 

 

문자열 처리 함수

 

strcpy() - 문자열 복사

 

 

strcmp() - 두 문자열의 크기를 비교

 

 

 

공백이 있는 문자열 입력

 

 

예시1

#include <stdio.h>
#include <string.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)

int main()
{
	char grade, name[10], reply[10];
	char correct[10] = "로딩중";

	printf("받고 싶은 C언어 등급은? ");
	scanf("%s", &grade);

	printf("\n%c를 받기 위해 노력 중이군요!", grade);

	printf("\n이름은? ");
	scanf("%s", &name);

	printf("%s님 반갑습니다!", name);

	printf("\n문제: 세상에서 가장 느린 중학교는? ");
	scanf("%s", &reply);

	if (strcmp(reply, correct) == 0)
	{
		printf("맞았습니다!");
	}

	else
	{
		printf("틀렸습니다! 정답은 %s!\n", correct);
	}

	return 0;
}

 

예시2

#include <stdio.h>
#include <string.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)
#define N 5

int main()
{
	char std[N][10] = { "최고수", "진재일", "강인", "나태희", "유명인" };

	int i, quiz[N] = { 10, 9, 8, 7, 9 };

	for (i = 0; i < N; i++)
	{
		printf("%s   %d\n", std[i], quiz[i]);
	}

	return 0;
}

 

 

예시3

#include <stdio.h>
#include <string.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)
#define N 5

int main()
{
	char std[N][6] = { "itsme", "ace", "iam", "myid", "snow"};
	char input[6];
	int i;

	printf("아이디는? ");
	scanf("%s", &input);

	for (i = 0; i < N; i++)
	{
		if (strcmp(input,std[i]) == 0)
		{
			printf("%s님 반갑습니다.", input);
			break;
		}
	}

	if (i == N)
	{
		printf("없는 아이디입니다.");
	}


	return 0;
}

'프로그래밍 > C,C++' 카테고리의 다른 글

포인터  (0) 2024.06.06
다양한 함수와 변수의 참조 범위  (0) 2024.05.30
인수 전달하는 함수  (0) 2024.05.28
인수 전달하지 않는 함수  (0) 2024.05.27
자료 배열 2 (9장)  (0) 2024.05.27

인수 전달이 있는 함수의 정의와 호출을 예시를 통해 바로 이해해보자.

 

#include <stdio.h>
#include <stdlib.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)

void f_line(char ch, int n);   // 함수 원형 선언

int main()                    // main 함수 정의
{ 
	int length = 10;          // main 함수의 지역 변수 선언 

	f_line('>', 5);         // '>'를 5개 출력하도록 함수 호출 
	f_line('-', length);
	f_line('<', length + 5);

	return 0;
}

void f_line(char ch, int n)    
{
	int i;

	for (i = 0; i < n; i++)
	{
		printf("%c", ch);
	}
	printf("\n");

	return;
}

 

 

여기서 보면 void f_line(char ch, int n)을 확인할 수 있을 것이다. 

이게 어찌보면 가장 중요한데 함수의 원형 선언을 할 때도 이렇게 매개변수를 설정해줘야 하고

밑에서 함수를 구성할 때도 꼭 들어가야하는 것이다. 

 

 

주의

  • 매개변수는 함수의 지역 변수이며, 호출되면 함수 헤더에서 바로 인수를 저장하는 데 사용되므로 ( ) 안에서 선언해야한다.
  • 매개변수는 무조건 개별적으로 선언해야한다. (int a, b는 오류!!!, >> int a, int b로 해야한다.)
  • 인수와 매개변수는 개수, 자료형, 순서가 같아야한다.
  • 함수 안에서 선언한 지역 변수는 함수 안에서만 유효하므로 인수와 매개변수의 이름은 서로 연관이 없다.

       >> 인수명과 매개변수명이 같아도 서로 다른 기억장소를 사용하는 다른 변수 즉, 동명이인에 해당한다. 

       >> 서로 다른 함수들은 서로의 정보를 알 수 없다. (라고 생각!)

 

 

인수 전달과 반환값이 있는 함수의 정의와 호출

#include <stdio.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)

int f_smaller(int n1, int n2);

int main()
{
	int s1, s2, s3, min;

	printf("점수1 점수2 점수3? ");
	scanf("%d %d %d", &s1, &s2, &s3);

	min = f_smaller(s1, s2);
	min = f_smaller(min, s3);

	printf("%d, %d, %d 중 가장 낮은 점수는 %d", s1, s2, s3, min);

	return 0;
}

int f_smaller(int n1, int n2)
{
	int min;

	if (n1 > n2)
	{
		min = n2;
	}

	else
	{
		min = n1;
	}

	return min;
}

 

 

begin ~ end까지의 합을 반환하는 함수

#include <stdio.h>
#include <stdlib.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)

int get_sum(int begin, int end);

int main()
{
	int a, b;

	printf("[1] a ~ b 합 구하기\n");

	printf("a는? ");
	scanf("%d", &a);


	printf("b는? ");
	scanf("%d", &b);

	printf("\n%d~%d 합: %d", a, b, get_sum(a, b));

	printf("\n\n[2] a*2 ~ b*2 합 구하기\n");
	printf("\n%d~%d 합: %d", a*2, b*2, get_sum(a*2, b*2));

	return 0;
}


int get_sum(int begin, int end)
{
	int sum, i;

	sum = 0;
	for (i = begin; i <= end; i++)
	{
		sum += i;
	}

	return sum;
}

'프로그래밍 > C,C++' 카테고리의 다른 글

다양한 함수와 변수의 참조 범위  (0) 2024.05.30
문자열 처리  (0) 2024.05.28
인수 전달하지 않는 함수  (0) 2024.05.27
자료 배열 2 (9장)  (0) 2024.05.27
자료 배열 1 (8장)  (0) 2024.04.29

함수

특정한 일을 수행하는 코드 블록

평균, 순위, 합격자 수, 둘 중 큰 값, 90점 이상 점수 개수와 같이 특정 값 한 개를 많이 구하는 데 많이 사용한다.

종류가 2개가 있는데 반환값이 있는 함수 반환값이 없는 함수로 나뉜다.

 

 

#include <math.h>가 필요한 수학 함수 

 

 

#include <stdlib.h>가 필요한 범용 함수

 

 

 

함수의 원형 선언 

함수의 원형 선언이 필요한 경우의 예시가 있고 그렇지 않은 예시가 있다.

보통 함수의 원형 선언이 필요한 경우를 권장하는 편이다.

줄 출력하기 예시를 통해 알아보자.

 

함수의 원형 선언이 필요한 경우 

#include <stdio.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)

void f_line();

int main()
{
	printf("줄 출력하기\n");

	f_line();
	f_line();

	return 0;
}

void f_line()
{
	int i;

	for (i = 0; i < 20; i++)
	{
		printf("=");
	}
	printf("\n");

	return;
}

 

함수의 원형 선언이 필요하지 않은 경우 

 

프로그램의 전체의 흐름은 main() 함수에 있으므로 main() 함수 정의를 맨 위에 두는 것이 바람직하며, 함수 원형을 main() 함수 위에 모두 모으면 이 프로그램에 포함된 함수로 어떤 것들이 있는지 쉽게 파악할 수 있으며, 함수를 호출할 때 해당 함수의 원형을 빠르게 찾아 참고하여 함수를 정확하게 호출할 수 있기 때문이다. 

#include <stdio.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)

void f_line()
{
	int i;

	for (i = 0; i < 20; i++)
	{
		printf("=");
	}
	printf("\n");

	return;
}

int main()
{
	printf("줄 출력하기\n");

	f_line();
	f_line();

	return 0;
}

 

 

주사위 눈의 수 맞히기, 숫자 암기, 구구단 게임

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#include <Windows.h>

#pragma warning(disable: 4996)
#pragma warning(disable: 6031)

void game_die();
void game_memory();
void game_99();

int main()
{
	int game;

	printf("어떤 게임을 할까요(1. 주사위  2. 숫자 암기  3. 구구단)? ");
	scanf("%d", &game);

	srand(time(NULL));

	switch (game)
	{
		case 1: game_die(); break;
		case 2: game_memory(); break;
		case 3: game_99(); break;
		default: printf("잘못 입력했습니다.");
	}

	return 0;
}


void game_die()
{
	int num, die_spot;

	die_spot = rand() % 6 + 1;

	printf("\n주사위를 던졌습니다. 얼마일까요(1 ~ 6)?");
	scanf("%d", &num);

	die_spot == num ? printf("\n%d인지 어떻게 알았죠? 찍기 왕이군요!", die_spot) :
		printf("\n%d인데 아쉽군요!\n", die_spot);
}


void game_memory()
{
	int n1, n2, n3, r1, r2, r3;

	n1 = rand() % 900 + 100;
	n2 = rand() % 900 + 100;
	n3 = rand() % 900 + 100;

	printf("\n화면에 나타난 값 3개 암기하기!!\n");
	printf("아무 키나 누르면 시작해요. ");
	getch(); system("cls");

	printf("\n%4d %4d %4d \n", n1, n2, n3);

	Sleep(2000); system("cls");

	printf("무슨 값(차례대로 빈칸으로 구분)? ");
	scanf("%d %d %d", &r1, &r2, &r3);

	(n1 == r1) && (n2 == r2) && (n3 == r3) ?
		printf("\n암기력 짱! \n") : 
		printf("\n아쉽게도 틀렸네요~ \n");

	return;
}

void game_99()
{
	int n1, n2, reply; 

	printf("구구단 게임입니다.\n");

	n1 = rand() % 8 + 2;
	n2 = rand() % 8 + 2;

	printf("%d * %d = ?", n1, n2);
	scanf("%d", &reply);

	reply != n1 * n2 ?
		printf("%d인데 틀렸습니다! ㅋㅋ  \n", n1 * n2) :
		printf("맞았습니다. ^^ \n");

	return;
}

'프로그래밍 > C,C++' 카테고리의 다른 글

문자열 처리  (0) 2024.05.28
인수 전달하는 함수  (0) 2024.05.28
자료 배열 2 (9장)  (0) 2024.05.27
자료 배열 1 (8장)  (0) 2024.04.29
C,C++ - 제어문 (while, do~while문)  (0) 2024.04.08

예시로 먼저 학습하기

 

모든 값의 순위 저장하기

#include <stdio.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)
#define NUM_OF_SCORE 5

int main()
{
	int scores[NUM_OF_SCORE] = { 97, 85, 89, 72, 96 };
	int ranks[NUM_OF_SCORE] = { 0 }, i, target;

	for (target = 0; target < NUM_OF_SCORE; target++)
	{
		ranks[target] = 1;
		for (i = 0; i < NUM_OF_SCORE; i++)
		{
			if (scores[target] < scores[i])
			{
				ranks[target]++;
			}
		}
	}

	for (target = 0; target < NUM_OF_SCORE; target++)
	{
		printf("%d번째: ", target + 1);
		printf("%d점의 순위: ", scores[target]);
		printf("%d위\n", ranks[target]);
	}

	return 0;
}

 

여기서는 순위를 매겨야하기에 ranks[NUM_OF_SCORE] = {0}을 생각해낼 수 있어야 하는 거 같다.

 

이걸을 먼저 선언하고 초기화할 수 있어야 똑바로 순위를 매겨서 출력을 할 수 있기 때문이다. 

 

 

2차원 배열에 저장하기 

 

2차원 배열 선언 및 참조

2차원 배열 선언

int scores[3][5];
double rates[10][15];

 

 

2차원 배열 초기화

int scores[3][5] = {{97, 85, 89, 72, 96}, {96, 94, 90, 88, 89}, {80, 85, 79, 70, 82}}

 

int scores[][5] = {{97, 85, 89, 72, 96}, {96, 94, 90, 88, 89}, {80, 85, 79, 70, 82}}

 

2차원 배열에서는 초기화 값이 있을 때 행수만 생략이 가능하다. 권장하지 않는다. 

 

또 다음과 같이 단일 중괄호를 사용하여 초기화할 수 있으나 권장하지 않는다.

int scores[3][5] = {97, 85, 89, 72, 96, 96, 94, 90, 88, 89, 80, 85, 79, 70, 82}

 

 

주의! 

기본변수는 선언 후 필요할 때마다 변수의 값을 대입 연산자를 사용하여 수정할 수 있으나 

배열은 선언 이후에 중괄호를 사용하여 변경할 수 없다. 

 

즉 아래와 같이 사용해서는 안된다!!!!

int scores[3][5]

scores = {{97, 85, 89, 72, 96}, {96, 94, 90, 88, 89}, {80, 85, 79, 70, 82}}
scores[3][5] = {{97, 85, 89, 72, 96}, {96, 94, 90, 88, 89}, {80, 85, 79, 70, 82}}

// scores 또는 scores[3][5]에 대입연산자를 사용하여 일괄적으로 할당할 수 없다!

 

 

2차원 배열 원소 참조

int scores[3][5] = {{97, 85, 89, 72, 96}, {96, 94, 90, 88, 89}, {80, 85, 79, 70, 82}}

//원소 참조//
scores[0][0] // 97로 나옴.//

 

 

 

2차원 배열 입력 및 출력

#include <stdio.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)
#define NUM_OF_STD 3
#define NUM_OF_SBJ 5

int main()
{
	int scores[NUM_OF_STD][NUM_OF_SBJ], i, j;

	for (i = 0; i < NUM_OF_STD; i++)
	{
		printf("%d번째 학생의 %d과목 성적입력: ", i + 1, NUM_OF_SBJ);
		for (j = 0; j < NUM_OF_SBJ; j++)
		{
			scanf("%d", &scores[i][j]); //항상 주의! "%d  "을 하면 안된다!!//
		}
		
	}

	for (i = 0; i < NUM_OF_STD; i++)
	{
		printf("%d번째 학생의 성적: ", i + 1);

		for (j = 0; j < NUM_OF_SBJ; j++)
		{
			printf("%5d", scores[i][j]);
		}
	}

	return 0;
}

 

 

 

과목 합계 및 평균

#include <stdio.h>

#define NUM_OF_STD 3
#define NUM_OF_SBJ 5
int main()
{
	int scores[NUM_OF_STD][NUM_OF_SBJ] = {
				{97, 85, 89, 72, 96},
				{96, 94, 90, 88, 89},
				{80, 85, 79, 70, 82}
	};
	int std, sbj, total;
	int std_tot; //한 학생의 성적 합계를 저장하기 위한 변수

	printf("번호  국어  영어  수학  물리  과학  총점   평균\n");
	printf("==============================================\n");

	//성적 출력하고 합계 구하기
	total = 0;
	for (std = 0; std < NUM_OF_STD; std++) {
		printf("%d번 [", std + 1);
		std_tot = 0;  //성적의 합계를 초기화
		for (sbj = 0; sbj < NUM_OF_SBJ; sbj++) {
			std_tot += scores[std][sbj]; //성적을 누적
			total += scores[std][sbj];
			printf("%5d ", scores[std][sbj]);
		}
		//tot, NUM_OF_SBJ가 정수이므로 (double)로 형변환하여 평균 구하기
		printf(" ] %5d %5.1f\n", std_tot, (double)std_tot / (double)NUM_OF_SBJ);
	}
	printf("==============================================\n");


	printf("전체 총점 %5d점\n", total);
	printf("평균 %.1lf점", (double)total / (NUM_OF_SBJ * NUM_OF_STD));

	return 0;
}

 

 

보너스

#include <stdio.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)

#define NUM_OF_STD 3
#define NUM_OF_SBJ 5
int main()
{
	int std, sbj, sum_sbj;
	int scores[NUM_OF_STD][NUM_OF_SBJ];
	int tot_sbj, total = 0;
	double avg_sbj;

	for (std = 0; std < NUM_OF_STD; std++)
	{
		printf("std %d: ", std + 1);
		for (sbj = 0; sbj < NUM_OF_SBJ; sbj++)
		{
			scanf("%d", &scores[std][sbj]);
		}
	}

	printf("\n");

	for (std = 0; std < NUM_OF_STD; std++)
	{
		sum_sbj = 0;
		printf("std %d: ", std + 1);
		for (sbj = 0; sbj < NUM_OF_SBJ; sbj++)
		{
			printf("%5d", scores[std][sbj]);
			sum_sbj += scores[std][sbj];
		}
		
		printf("|%5d|%5.1lf\n", sum_sbj, (double) sum_sbj / NUM_OF_SBJ);
	}


	printf("tot: ");
	for (sbj = 0; sbj < NUM_OF_SBJ; sbj++)
	{
		tot_sbj = 0;
		for (std = 0; std < NUM_OF_STD; std++)
		{
			tot_sbj += scores[std][sbj];
		}
		total += tot_sbj;
		printf("%5d", tot_sbj);
	}
	printf("|%5d|%5.1lf\n", total, (double)total / (NUM_OF_SBJ * NUM_OF_STD));
	
	printf("avg: ");
	for (sbj = 0; sbj < NUM_OF_SBJ; sbj++)
	{
		tot_sbj = 0;
		for (std = 0; std < NUM_OF_STD; std++)
		{
			tot_sbj += scores[std][sbj];
			avg_sbj = (double)tot_sbj / NUM_OF_STD;
		}
		printf("%5.1lf", avg_sbj);
	}

	return 0;
}

 

결과창

 

'프로그래밍 > C,C++' 카테고리의 다른 글

인수 전달하는 함수  (0) 2024.05.28
인수 전달하지 않는 함수  (0) 2024.05.27
자료 배열 1 (8장)  (0) 2024.04.29
C,C++ - 제어문 (while, do~while문)  (0) 2024.04.08
C,C++ - 반복문  (0) 2024.04.03

다중선형회귀

변수 Y를 X₁,....,Xp로 설명하는 다중선형회귀 모형

 

회귀계수의 추정

잔차

 

 

 

손실분량 

 

최소제곱법

data("stackloss")
m1 = lm(stack.loss ~ ., data = stackloss); m1

여기서 . 의 의미는 stack.loss를 제외한 모든 변수를 덧셈으로 연결한다는 뜻.

 

 

후버의 M-추정법

m2 = rlm(stack.loss ~ ., data = stackloss); m2

 

LMS

m3 = lqs(stack.loss ~ ., data = stackloss, method = "lms"); m3

 

LTS

m4 = lqs(stack.loss ~ ., data = stackloss, method = "lts"); m4

최소제곱 추정 방법 

  • 개체에서 발생하는 잔차에 제곱을 취하여 합을 구하는 방식 
  • 큰 절대값 잔차가 발생하지 않도록 회귀직선을 움직임. 
  • 한 개의 특이점이 회귀직선의 결정에 막중한 영향력을 가짐.

 

후버의 M-추정 

최소제곱 추정은 함수

으로 정의할 떄

를 최소화

그런데 이 추정치가 안정적이지 못한 이유는 함수가 z²에 비례하는 형태이기 때문 

 

따라서 다음의 변형을 고려한다.

|z| < c에서는 원래의 함수와 같지만 그 밖에서는 원래 함수보다 천천히 증가한다.

 

이를 반영한 후버의 M-추정량은 

로 얻어진다.

여기서 σ를 모르기 때문에 (다음 페이지의) MAD로 추정해 사용한다.

 

MAD (median of absolute deviations)

를 가정하고 σ에 대한 로버스트 추정량으로 IQR / 1.35 를 사용한다.

MAD는 또 다른 로버스트 추정량으로

라 정의한다.

 

 

목적함수  l   (z)  vs.   l (z)

ell.0 = function(z){z^2}
ell.1 = function(z){c = 1.345; ifelse(abs(z) < c, z^2, c*(2*abs(z) - c))}
z = seq(-6, 6, 0.1)
y.0 = ell.0(z)
y.1 = ell.1(z)
par(mfrow = c(1,2), pty = 's', mar = c(5, 3, 1, 3))
plot(y.0 ~ z, type = "l", lwd = 2, ylim = c(0, 30),
     ylab = expression(paste("\u2113")[0](z)))
plot(y.1 ~ z, type = "l", lwd = 2, ylim = c(0, 30),
     ylab = expression(paste("\u2113")[1](z)))
par(new = T)
plot(y.0 ~ z, type = "l", lwd = 2, lty = "dotted", ylim = c(0, 30), ylab = "")

 

코드해석: 

1. ell.0과 ell.1은 앞에서 봤던 l ₀, l ₁의 목적함수를 뜻한다. 

2. ell.1에서 ifelse는 첫 번째 조건을 만족하면 z² 을 하고 아니면 c * (2 * abs(z) - c)를 한다.

 

코드결과:

 

 

LMS/ LTS 추정 

> 자료점을

라 하고, 잔차를 

라 할 때, 최소제곱법은 

을 풀어 회귀직선을 얻는다.

 

 

LMS

> 반면에 LMS (Least Median of Squares) 회귀는 로버스트성의 관점에서 이 목적식을 

으로 개량한다.

 

> 최소제곱법은 Least Squares지만 Least Mean of Squares으로 간주 가능하다.

> LS는 선형대수적 풀이가 가능하지만 LMS는 몬테카를로 반복시행으로 해를 찾는다.

 

 

LTS

> LTS (Least Trimmed Squares; 최소절삭제곱) 회귀는

을 풀어서 회귀직선을 얻는다.

 

> q는 잔차제곱에 대한 순서통계량

에서의 순서

 

> default는 n의 50%정도에 해당하는 [n/2] + [(p+1)/2]

> n의 75% 정도에 맞추면 추정치의 정확도가 높아진다는 연구결과가 있다. 

 

비교1

library(MASS)
m0 = lm(y ~ x)  # 최소제곱추정 
m1 = rlm(y ~ x)  # 후버의 M-추정
m2 = lqs(y ~ x, method = "lms") # lms
m3 = lqs(y ~ x, method = "lts") # lts 
par(mfrow = c(1,1), mar = c(5, 3, 1, 3), pty = 's')
plot(y ~ x, ylim = c(-10, 10), cex = 2, pch = 19, col = 'darkgreen')
abline(m.truth$coefficients, lty = "dotted", lwd = 2)
abline(m0$coefficients, col = "darkcyan", lwd = 2)
abline(m1$coefficients, col = "darkblue", lwd = 2)
abline(m2$coefficients, col = "darkred", lwd = 2)
abline(m3$coefficients, col = "orange", lwd = 2)

여기서 보면 최소제곱추정 (lm)은 이상치에 민감해서 이상치쪽으로 방향이 따라간다는 걸 알 수 있다. 

lms, lts, 후버의 m-추정 모두 경향을 잘 따라가고 있다. 

 

다른 예를 통해서 조금 더 자세하게 살펴보자.

 

 

 

비교2 

1. X: 1,2,3,... , 10으로 고정

2. Y: Y = 2.5 + 0.5X + ε, ε ~ N(0,1)으로 생성 

3. 열번 째 자료점은 (100, -100)으로 수정 

  • x = 100은 '특이한' 설명변수 값
  • x = 100이 정상적인 값이라 했을 때도 y = -100은 특이값 ('회귀' 특이점)
set.seed(1234567)
x = seq(1:10)
y = 2.5 + 0.5*x + rnorm(10, 0, 1)
x[10] = 100
y[10] = -100

par(mar = C(5,3,1,3), pty = 's')
plot(y ~ x, ylim = c(-100, 10), cex = 1, pch = 19, col = 'darkgreen')
abline(m.truth$coefficients, lty = 'dotted', lwd = 2)

 

후버의 M-추정 vs. LMS

m1 = rlm(y ~ x)  # 후버의 M-추정
m2 = lqs(y ~ x, method = "lms") # lms
par(mar = c(5,3,1,3), pty = 's', mfrow = c(1,2))
plot(y ~ x, ylim = c(-100, 10), cex = 1, pch = 19, col = 'darkgreen')
abline(m.truth$coefficients, lty = 'dotted', lwd = 2)
abline(m1$coefficients, col = 'darkblue', lwd = 2)
abline(m2$coefficients, col = 'darkred', lwd = 2)

plot(y ~ x, ylim = c(-100, 10), cex = 1, pch = 19, col = 'darkgreen')
abline(m.truth$coefficients, lty = 'dotted', lwd = 2)
abline(m1$coefficients, col = 'darkblue', lwd = 5)
abline(m2$coefficients, col = 'darkred', lwd = 5)

LMS가 후버의 M-추정에 비해 더 경향을 잘 따라간다. 

 

 

LTS default vs. LTS quantile = 8

m2a = lqs(y ~ x, method = "lts")
m2b = lqs(y ~ x, method = "lts", quantile = 8)
par(mar = c(5,3,1,3), pty = 's')
plot(y ~ x, xlim = c(0, 10), ylim = c(0, 10), cex = 2, pch = 19, col = "darkgreen")
abline(m.truth$coefficients, lty = 'dotted', lwd = 2)
abline(m1$coefficients, col = 'darkcyan', lwd = 5)
abline(m2$coefficients, col = 'purple', lwd = 5)

누가 더 경향을 더 잘 따라간다고 할 수는 없다. 

'프로그래밍 > R프로그래밍' 카테고리의 다른 글

EDA - 다중선형회귀  (2) 2024.05.22
EDA - 줄기와 잎 그림  (0) 2024.04.08
R프로그래밍 - simulation (2)  (1) 2023.12.05
R프로그래밍 - 중심극한정리 확인  (1) 2023.12.04
R프로그래밍 - simulation  (0) 2023.12.04

 ACF(자기상관 함수 - 왼쪽) 플롯은 시계열 데이터의 현재 값과 과거 값 사이의 상관 관계를 시간 지연에 따라 보여준다.

PACF(부분 자기상관 함수 - 오른쪽)플롯은 시간 지연 간의 상관 관계를 보여준다. 이는 다른 지연의 영향을 배제한 후의 상관 관계를 나타낸다. 

 

 

ACF 그래프는 라그(lag) 증가함에 따라 상관계수가 천천히 감소하는 패턴을 보여준다.

일반적으로 이런 패턴은 데이터에 계절성이나 추세와 같은 비정상적인 성분이 포함되어 있을 수 있음을 나타낸다.

그러나 이 경우, ADF 테스트를 통해 데이터가 정상 시계열임을 확인했기에 ACF 그래프에서 나타나는 느린 감소나 주기적 패턴에도 불구하고, 우리는 이 데이터를 정상적인 시계열로 간주하고 ARIMA 모델링에 사용할 것이다.

이로 인해 비정상 시계열로 보일 수 있는 신호가 있더라도, 이미 검증된 데이터의 정상성을 신뢰하고 해당 데이터로 모델링을 진행할 것이다. 

 

 

PACF 그래프에서 신뢰구간 안으로 처음으로 들어가는 라그(lag)는 인덱스 9번이다. 

이는 해당 지점까지의 부분 자기상관계수는 통계적으로 유의미하다고 해석할 수 있으며, 이는 ARIMA 모델에서 자기회귀(AR)부분의 p 매개변수를 결정할 때의 정보를 제공한다.

첫 번째 라그 이후 상관계수가 점차 감소하는 것은 이전 시점의 데이터가 현재값에 영향을 미칠 수 있음을 나타내지만, 9번째 라그 이후에는 이러한 영향이 통계적으로 유의하지 않게 되어 추가적인 AR 구성 요소가 필요하지 않을 수 있음을 의미한다. 

이 정보를 기반으로, ARIMA 모델링 시 p를 최대 8로 설정할 수 있으며, 이는 시계열 데이터의 현재값이 과거 8개의 시점에 영향을 받을 수 있다는 가정 하에 모델을 구축하는 것이다. 

이후 모델 선택 과정에서 AIC와 같은 정보 기준을 통해 더 낮은 p값이 더 적합한 모델을 제공하는지 평가할 수 있다. 

 

정상성

시계열 데이터의 통계적 속성이 시간에 따라 변하지 않음을 의미하는데, 데이터의 평균과 분산이 일정함을 의미한다.

 

adfuller() 함수를 통해 시계열 데이터의 정상성을 검증하기 위한 Augmented Dicker-Fuller 테스트를 수행한다.

from statsmodels.tsa.stattools import adfuller

# ADF 테스트
adf_result = adfuller(train_data['평균기온'])
print(f'ADF 통계값: {adf_result[0]}')
print(f'p-value: {adf_result[1]}')

 

ADF 테스트

ADF 테스트는 '단위근'이라는 특정 유형의 비정상성을 확인한다.

 

이 테스트에서 계산되는 ADF 통계치는 데이터에 단위근이 없다는 대립 가설에 대한 증거를 제공한다.

 

p - value는 이 통계치가 얼마나 유의미한지를 나타낸다. 

일반적으로 p-value가 0.05 이하일 경우, 우리는 데이터가 정상성을 가지고 있다고 간주하고 귀무 가설을 기각할 수 있다.

 

 

예를 들어

만약 코드 실행 결과, ADF 통계값은 -2.904, p-value는 0.044로 나타났을 경우, 일반적으로 p-value가 0.05 이하일 경우에는 귀무가설을 기각하고 데이터가 정상성을 가진다고 할 수 있지만...

이 경우에는 p-value가 0.05에 근접하여 데이터가 정상 시계열이라고 결론짓기에는 약간의 불확실성이 남는다.

 

따라서 이 경우, 차분을 수행하여 정상성을 확인하거나 모델의 성능을 검증하여 평가할 수 있다. 

 

혹은 p와 q의 범위 내에서 여러 ARIMA 모델을 피팅하고 각 모델의 AIC(Akaike Information Criterion) 값을 비교함으로써 최적의 파라미터 조합을 찾는 방법을 사용할 수도 있다.

 

AIC는 모델의 적합도와 복잡도를 동시에 고려하는 척도로, 낮은 AIC 값을 가지는 모델이 주어진 데이터에 대해 더 좋은 예측 성능을 제공할 가능성이 높다. 

'프로그래밍 > 프로젝트' 카테고리의 다른 글

쇼핑몰 지점별 매출 예측 AI  (0) 2024.08.29
자기상관분석  (0) 2024.05.21
ARIMA 모델 검증 및 예측 정확도 평가  (0) 2024.05.21
ARIMA 모델  (0) 2024.05.21
데이콘 - 고객 대출 등급 분류 프로젝트  (1) 2024.02.09

평균 절대 오차 (Mean Absolute Error, MAE)

 

MAE는 회귀 문제에서 모델의 정확도를 평가하는데 사용되는 방법 중 하나이다.

 

MAE는 실제값과 예측값 간의 차이의 절대값의 평균을 의미한다. 

이는 모델의 예측이 얼마나 정확한지를 나타내는 지표로, 값이 낮을수록 더 정확한 예측을 의미한다. 

 

장점

  • 모든 오차의 절대값을 평균을 계산하므로, 예측하고자 하는 단위가 동일하다. (예: MAE가 5라면 모델이 평균적으로 실제값과 5정도 차이남을 나타낸다.)
  • 다른 평가 지표에 비해 이상치에 덜 민감하다.

 

단점

  • 모든 오차를 동일하게 취급하므로, 특정 오차에 가중치를 주고싶을 경우 부적절한 평가방법일 수 있다.

 

'프로그래밍 > 프로젝트' 카테고리의 다른 글

자기상관분석  (0) 2024.05.21
시계열 데이터의 정상성  (0) 2024.05.21
ARIMA 모델  (0) 2024.05.21
데이콘 - 고객 대출 등급 분류 프로젝트  (1) 2024.02.09
고객 유지를 위한 필요한 행동 예측  (1) 2024.01.14

ARIMA 

- 자기회귀 누적 이동 평균 (Autoregressive Integrated Moving Average)의 약자

- 시계열 데이터의 과거 값과 오류를 사용하여 미래 값을 예측

- 이 모델은 자기회귀(AR), 차분(I), 이동평균(MA) 세 가지의 구성 요소로 이루어져 있음.

 

자기회귀(AR)

'자기회귀'는 과거의 값들이 미래 값에 어떤 영향을 미치는지를 설명한다.

예를 들어, 지난 주의 판매량이 이번 주의 판매량에 영향을 미치는 경우, 이는 자기 회귀 관계에 해당한다.

AR 부분에서는 'p'라는 파라미터를 사용하는데, 이는 과거 데이터 포인트 중 얼마나 많은 것을 고려할 것인지를 결정한다.

 

차분(I)

'차분'은 비정상적인 시계열 데이터를 정상적인 상태로 만들기 위해 사용된다.

비정상 시계열은 평균, 분산 등이 시간에 따라 변하는 특성을 가진다.

차분은 이러한 데이터의 추세나 계절성을 제거하여 시간에 따라 일정한 수준을 유지하도록 한다.

 

이동평균(MA)

'이동평균' 부분은 과거 예측 오차가 미래 값에 어떻게 영향을 미치는지를 나타낸다. 

MA에서는 'q'라는 파라미터를 사용하여 과거 예측 오차 중 몇 개를 고려할 것인지를 결정한다. 

model = ARIMA(train_data['평균기온'], order = (4, 3, 2))

 

위 코드는 ARIMA 모델을 초기화한다. 

여기서 train_data['평균기온']은 모델에 사용할 시계열 데이터이다.

order = (4, 3, 2)는 ARIMA 모델의 세 가지 주요 파라미터를 설정한다.

4, 3, 2는 각각 p,q,d를 의미하며

p는 자기회귀 부분의 차수, d는 차분의 차수, q는 이동평균 부분의 차수를 의미한다. 

 

 

시계열 데이터의 summary()

1. Log Likelihood

: 모델이 데이터를 얼마나 잘 적합하는지를 나타내는 지표, 이 값이 클수록 모델이 데이터를 더 잘 설명한다는 뜻 

 

2. AIC / BIC

: 모델의 적합도를 평가하고 모델 간 비교를 할 때 사용되는 지표이다. 낮은 값일 수록 모델의 적합성이 더 높음을 의미.

 

3. coef

: 이 계수는 AR과 MA 파라미터의 영향력을 나타낸다.

: 예를 들어, 하나의 계수가 3.21이라면, 계수가 양수이고 따라서 그것의 지연(lag) 값은 현재 값에 긍정적인 영향을 미친다는 뜻이다. 즉, 과거의 값이 높으면 현재 값도 높을 것으로 예상된다는 뜻이다.

 

4. P > |z|

: 각 계수의 통계적 유의미함을 나타내는 p-값입니다. 일반적으로 이 값이 낮으면 (예: 0.05 이하), 해당 계수가 통계적으로 유의미하다고 할 수 있다. 

 

5. Ljung-Box / Jarque-Bear / Heteroskedasticity(이분산성)

: 이 테스트들은 각각 잔차가 무작위 노이즈인지, 정규분포를 따르는지, 등분산성을 가지는지 평가한다.

 

6. Prob(Q, JB, H)

: p-값들이 낮다면(0.05 또는 0.01이하), 잔차가 무작위 노이즈의 특성을 가지며, 정규분포를 따르고, 시차에 따라 일정한 분산을 가진다고 볼 수 있다. 

 

+ Recent posts