본문 바로가기

헷갈릴만한 개념

R에서 for문 밖에 함수를 정의하면 생기는 문제

https://pastryofjsmath.tistory.com/53

 

R에서 for문 안에 함수를 정의하면 생기는 문제

매우 쉬운 문제입니다. x의 범위를 [0,1]에서 점별수렴성을 본다면 n->inf일 때 0으로 수렴하겠죠.하지만 괴랄한 수식의 수렴성을 판단 할 때도 있지 않을까해서 이 문제를 가지고 시각화 코드를 짜

pastryofjsmath.tistory.com

위의 내용과 이어집니다.

 

멱급수

위와 같은 식을 멱급수라고 하는데, 멱급수는 앞이 등차수열꼴이고 뒤가 등비수열꼴입니다.

우선 각 항(n개)들을 그려보도록 하겠습니다. 각 항을 포인트로 찍는 작업입니다.

fun <- function(n) n*2^n
x <- seq(0,10,by=1)
df <- data.frame(x=x,y=fun(x))
df
ggplot(data = df,aes(x = x,y=y,color='red'))+
  geom_point(stat='identity')+
  labs(title = 'n2^n 그래프',x='x',y='y')+
  theme_minimal()

위는 n*x^n (x=2)일때의 각 항의 점을 찍어둔 그래프입니다.

그렇다면 x=2가 아닌 일반화를 시킨 그래프는 어떻게 될까요?

#ex) 멱급수의 멱수열들을 ggplot으로 나타내보자.
func <- NULL
for(i in 1:15){
  func <- c(func,local({
    n <- i
    function(x) n*x^n}))
}

x <- seq(0,10,by=1)
x
#15개 함수를 data.frame으로 만들어야해.
df_list <- lapply(1:15,function(col){
  data.frame(x=x,y=func[[col]](x),fun_name= paste0(col,'x^',col))
})

df<-do.call(rbind,df_list)
df
library(ggplot2)
ggplot(data = df,aes(x = x,y=y,color=fun_name))+
  geom_line(stat='identity')+
  labs(title = 'nx^n 멱수열 그래프',x='x',y='y')+
  theme_minimal()

 

각 항들을 포인트로 찍어서 표시해두었습니다.

여기까지가 앞에서 한 복습이였습니다.

 

 

멱급수

위 S라는 그래프를 그려야하는데 어떻게 해야 할 지 생각을 해보니 반복문을 이용하면 될 것 같습니다.

sums <- 0
> for(i in 1:n){
+   sums<- sums + function() {i*2^i}
+ }
sums + function() {에서 다음과 같은 에러가 발생했습니다:이항연산자에 수치가 아닌 인수입니다

우리가 앞에서 했던대로 비슷하게 짜봤지만 안됩니다. 좀만 생각해보면 앞에서는 fun <- c(fun,function(x) ~ ) 즉 벡터로 묶어버렸고 이번에는 합입니다.

수학적으로 0 + 1*2 같이 문제가 없는데 프로그래밍에서 수와 함수는 합이 불가능합니다. 무슨말이냐면 앞에서 다룬내용과 유사합니다. 아래와 같이 0+i*2^i 같은 계산이 불가능하다는 것이죠.

> f <- function() {i*2^i}
> f
function() {i*2^i}
> 0+f
0 + f에서 다음과 같은 에러가 발생했습니다:이항연산자에 수치가 아닌 인수입니다

 

그렇다면 함수로 처리하지 않으면 되는것 아닌가?라는 생각이 들 수 있습니다.

n<- 15
sums <- 0
for(i in 1:n){
  sums<- sums + i*2^i
}
> sums
[1] 917506

당연하게 누적합이 나와버리죠.

다시 생각해보자면, 내가 원하는 것은 위의 식과 같은 항으로 구성된 식에서 (y1,y2,y3...,)같이 나와야 합니다.

다음과 같이 수정합니다.

> n<- 15
> sums <- 0
> a <- NULL
> for(i in 1:n){
+   sums<- sums + i*2^i
+   a <- c(a,sums)
+   }
> 
> a
 [1]      2     10     34     98    258    642   1538   3586
 [9]   8194  18434  40962  90114 196610 425986 917506

네 뭐 쓰려면 쓸 수는 있겠으나,  이번에는 i*2^i가 function()으로 정의 된 함수가 아니였기에 간단하게 가능했습니다.

하지만 다음과 같이 쓴다면 우리의 직관과 더 잘 맞는 식이 나오게 됩니다. 

 

sum_serize <- function(n){
  sum <- 0
  for(i in 1:n){
    sum <- sum + i*2^i
  }
  return(sum)
}

사용자 정의 함수 안에 for문을 넣은 형태입니다. 아래의 코드랑 비교해보면,

fun <- NULL
n <- 10
for( i in 1:n){
fun <- c(fun,function(x) x^i)
}

#fun[[1]](1) = fun[[2]](1) = .. =fun[[10]](1) = 10

fun은 마지막 i를 참조해서  모든 10개의 벡터 요소 x^i 들이 x^10으로 바뀌는 반면,

sum_serize의 sum은 1:n이라는 for문 구간을 걸어두고 실제로는 n을 지정하지 않으면 반복문이 안되나

 function(n){}을 밖에 사용하여 자체적인 함수가 되었습니다. for문 이후에도 사용자가 아직 끝값을 정하지 않은 것이죠.  이러면 뭐가 좋냐면

n<- 15
sums <- 0
for(i in 1:n){
  sums<- sums + i*2^i
}

#n =16,17,18를 확인하려면 
n <- 16
sums <- 0
for(i in 1:n){
  sums<- sums + i*2^i
}

n<-17
sums <- 0
for(i in 1:n){
  sums<- sums + i*2^i
}

n<-18
sums <- 0
for(i in 1:n){
  sums<- sums + i*2^i
}

매번 할 때마다 for문을 돌려야합니다.

그렇기에 수정이 편리하고 직관적인 형태는 함수안에 for문을 넣는 방식입니다.

 

이제 데이터프레임을 만들건데, 다음과 같은 문제가 발생합니다. 두 함수를 비교하세요.

> x <- seq(1,10,by=1)
> sum_serize(x)
[1] 2
경고메시지(들): 
1:n에서: numerical expression has 10 elements: only the first used
> fun <- function(x) x^2
> fun(x)
 [1]   1   4   9  16  25  36  49  64  81 100

사용자지정함수 안에 for문을 넣으면 단일벡터밖에 받지 못합니다. 당연하게도 for문을 사용한다 치면 벡터의 각 요소를 하나씩만 처리하기 때문이죠. 

 

그럼 어떻게 하면 되냐? 단일벡터만 받는다고 했습니다. 그럼 이 단일벡터는 리스트여도 되기때문에 lapply를 이용해줍니다.

df_list <- lapply(1:10,function(col){
  data.frame(x=col,y=sum_serize(col))
})
df <- do.call(rbind,df_list)
ggplot(df,aes(x=x,y=y))+
  geom_point(stat='identity')

다음은 위 코드에서 lapply를 써야 하는 이유입니다.

> df2 <- sapply(1:10,function(col){
+   data.frame(x=col,y=sum_serize(col))
+ })
> class(df2)
[1] "matrix" "array" 
> df2 <- t(df2)
> df2 %<>% as.data.frame()
> df2 %>% class()
[1] "data.frame"
> df2 %>% str()
'data.frame':	10 obs. of  2 variables:
 $ x:List of 10
  ..$ : int 1
  ..$ : int 2
  ..$ : int 3
  ..$ : int 4
  ..$ : int 5
  ..$ : int 6
  ..$ : int 7
  ..$ : int 8
  ..$ : int 9
  ..$ : int 10
 $ y:List of 10
  ..$ : num 2
  ..$ : num 10
  ..$ : num 34
  ..$ : num 98
  ..$ : num 258
  ..$ : num 642
  ..$ : num 1538
  ..$ : num 3586
  ..$ : num 8194
  ..$ : num 18434

class를 확인했을 때  sapply로 행렬형태로 반환되었고 이를 고치기 위해 전치를 시키며 데이터프레임으로 변환과정을 거쳐서 class가 data.frame이지만 str()을 사용한 결과 이들의 각 행들은 아직도 리스트인 상황이기에 lapply을 이용하는게 심신에 편합니다.

ggplot()도 오로지 data.frame만 받기 때문에 str()상 구조가 리스트이면 사용이 불가능합니다.

 

추가적으로 위 멱급수를 정리하면

위와 같은 식이 되는데 실제로 확인하면 똑같음을 알 수 있습니다.

# 위의 멱급수 계산 시  S = (n-1)*2^(n+1) +2 ####
fun <- function(n) (n-1)*2^(n+1) +2
x <- seq(1,10,by=1)
x
df2 <- data.frame(x=x,y=fun(x))
df2
df$y ==df2$y
> df$y ==df2$y
 [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE

 

정리하자면 

1. for문 안쪽에 사용자정의함수가 들어가면 반복문이 끝난 i를 참조해서 클로저문제가 발생함을 알 수 있었고

2. 이를 해결하기 위해 local()안에 i를 새로운 변수명으로 지정하고 함수를 선언했다.

3. 위 내용을 이용해 f(x,n)=x^n 과 같이 한 쪽을 증가시키는 함수도 그릴 수 있었다.

ex) poisson dist 에서 f(x,lambda)라는 pdf를 lambda를 조정해 위와 같은 방법으로 그릴 수 있다.

 

4. 멱급수를 나타내기 위해 for문 안에 사용자정의함수를 넣어서 더했지만 이는 불가능한 행동(인수+수치가 불가능)

5. 반복문에 벡터를 넣어 단일벡터(y_i)를 계속해서 집어넣는 과정과 사용자정의함수안에 for문을 넣는 방식둘 다 사용이 가능하지만 전자는 사용자정의함수가 없었고 n(항의 길이)을 내 맘대로 조정하려면 후자가 훨씬 낫다.

6.사용자정의함수안에 for문을 넣으면 변수값을 단일벡터만 받기에 이를 리스트로 생각해서 lapply을 사용한다.