[R 프로그래밍] 13. reshape2, magrittr

Author : tmlab / Date : 2016. 10. 4. 01:21 / Category : Lecture/R 프로그래밍

1. reshape2

reshape2는 Hadley Wickham에 의해 작성된 R 패키지로써 wide와 long포맷으로 쉽게 데이터를 바꿀수 있게 해줍니다

What makes data wide or long?

wide 포맷은 각 변수들을 전부 열로 할당한 포맷입니다. 예를 들면 다음과 같습니다.

#   ozone   wind  temp
# 1 23.62 11.623 65.55
# 2 29.44 10.267 79.10
# 3 59.12  8.942 83.90
# 4 59.96  8.794 83.97

long 포맷은 다음과 같습니다

#    variable  value
# 1     ozone 23.615
# 2     ozone 29.444
# 3     ozone 59.115
# 4     ozone 59.962
# 5      wind 11.623
# 6      wind 10.267
# 7      wind  8.942
# 8      wind  8.794
# 9      temp 65.548
# 10     temp 79.100
# 11     temp 83.903
# 12     temp 83.968

long 포맷 데이터는 가능한 변수들의 타입을 위한 열과 해당 변수들의 값을 위한 열을 가집니다. tidyr의 key와 value 열을 생각하시면 됩니다. 물론 long 포맷 데이터는 딱 두개의 열만 가질 필요는 없습니다. 예를 들면, 매년 일단위당 오존량의 데이터를 가지고 있을 때, 연(year)과 오존량(Ozone measurements)만이 아니라 일단위(1월 21일,22일…etc) 열도 가질 수 있습니다.

데이터 분석을 하다보면 wide 포맷이 필요할 때도 있고, long 포맷이 필요할 때도 있습니다. 현실적으로, long 포맷의 데이터가 일반적으로 많이 사용 하긴 합니다. (e.g., ggplot2(technically tidy data)나 plyr, 그리고 대부분의 모델링 함수들(lm(), glm(), gam() 등등)을 사용할때 long 포맷 데이터를 요구합니다.)

The reshape2 package

reshape2 는 기본적으로 두개의 핵심 함수를 기반으로 합니다: melt와 cast:

melt 함수는 wide 포맷 데이터를 long 포맷으로 변환해주는 함수입니다.

  • tidyr::gather()와 유사합니다.

이와 반대로 cast는 long 포맷 데이터를 wide 포맷으로 변환해주는 함수입니다.

  • tidyr::spread()와 유사합니다.

1.1. wide 포맷 데이터를 long 포맷으로 전환하기: melt함수

R에 내장되어 있는 airquality라는 데이터셋을 사용할 것입니다.

  • 먼저 데이터셋을 쉽게 변경하기 위해 각 열의 이름을 전부 소문자로 만들겠습니다.
library(reshape2)
names(airquality) <- tolower(names(airquality))
head(airquality)
##   ozone solar.r wind temp month day
## 1    41     190  7.4   67     5   1
## 2    36     118  8.0   72     5   2
## 3    12     149 12.6   74     5   3
## 4    18     313 11.5   62     5   4
## 5    NA      NA 14.3   56     5   5
## 6    28      NA 14.9   66     5   6

melt함수를 적용해보겠습니다.

aql <- melt(airquality) # [a]ir [q]uality [l]ong format
## No id variables; using all as measure variables
head(aql)
##   variable value
## 1    ozone    41
## 2    ozone    36
## 3    ozone    12
## 4    ozone    18
## 5    ozone    NA
## 6    ozone    28
tail(aql)
##     variable value
## 913      day    25
## 914      day    26
## 915      day    27
## 916      day    28
## 917      day    29
## 918      day    30

기본적으로, melt함수는 숫자 값을 가지고 있는 모든 열들은 value 변수라고 가정합니다. 이런 상황이 종종 내가 바라던 상황 일 수는 있지만, 지금은 일단위 오존량, 태양 복사열, 풍속 그리고 온도를 알고 싶습니다. 이럴 때 “ID variable”을 사용하면 설정을 해 줄 수 있습니다.

aql <- melt(airquality, id.vars = c("month", "day"))
head(aql)
##   month day variable value
## 1     5   1    ozone    41
## 2     5   2    ozone    36
## 3     5   3    ozone    12
## 4     5   4    ozone    18
## 5     5   5    ozone    NA
## 6     5   6    ozone    28

이제 이렇게 만들어진 long 포맷 데이터의 열 이름까지 지정을 해보도록 하겠습니다.

aql <- melt(airquality, id.vars = c("month", "day"),
  variable.name = "climate_variable", 
  value.name = "climate_value")
head(aql)
##   month day climate_variable climate_value
## 1     5   1            ozone            41
## 2     5   2            ozone            36
## 3     5   3            ozone            12
## 4     5   4            ozone            18
## 5     5   5            ozone            NA
## 6     5   6            ozone            28

melt 연습하기

다음과 같은 ChickWeight라는 R 기본 내장 데이터셋이 있습니다.

head(ChickWeight)
##   weight Time Chick Diet
## 1     42    0     1    1
## 2     51    2     1    1
## 3     59    4     1    1
## 4     64    6     1    1
## 5     76    8     1    1
## 6     93   10     1    1

해당 데이터셋에서 melt를 사용하여 ID variable을 time,chick,diet를 주고 weight를 value로 주는 chick_m이란 변수를 만들어보세요

##   Time Chick Diet variable value
## 1    0     1    1   weight    42
## 2    2     1    1   weight    51
## 3    4     1    1   weight    59
## 4    6     1    1   weight    64
## 5    8     1    1   weight    76
## 6   10     1    1   weight    93

1.2. long 포맷 데이터에서 wide 포맷 데이터로 전환하기: cast 함수

wide 포맷 데이터에서 long 포맷 데이터로 변환해주는 것은 매우 간단한 반면에 long 포맷에서 wide 포맷으로 변환하는 것은 좀 더 힘들 수도 있습니다.

reshape2 패키지에서는 여러가지 cast 함수가 존재합니다. 하지만 보통 데이터프레임 객체를 사용하기 때문에, dcast함수에 대해 설명 드리겠습니다.(벡터나 행렬, 배열 등을 반환할땐 acast를 사용합니다.)

cast함수를 사용하기 위해 앞에서 연습했던 aql 객체를 다시 만들어서 되돌려보겠습니다.

dcast는 데이터의 형태를 묘사하는 식을 사용합니다.

  • 왼쪽의 인수는 “ID variable”을 참조하고 오른쪽의 인수는 “measured variable(value)”를 참조합니다.
aql <- melt(airquality, id.vars = c("month", "day"))
head(aql)
##   month day variable value
## 1     5   1    ozone    41
## 2     5   2    ozone    36
## 3     5   3    ozone    12
## 4     5   4    ozone    18
## 5     5   5    ozone    NA
## 6     5   6    ozone    28
aqw <- dcast(aql, month + day ~ variable)
head(aqw)
##   month day ozone solar.r wind temp
## 1     5   1    41     190  7.4   67
## 2     5   2    36     118  8.0   72
## 3     5   3    12     149 12.6   74
## 4     5   4    18     313 11.5   62
## 5     5   5    NA      NA 14.3   56
## 6     5   6    28      NA 14.9   66
head(airquality)
##   ozone solar.r wind temp month day
## 1    41     190  7.4   67     5   1
## 2    36     118  8.0   72     5   2
## 3    12     149 12.6   74     5   3
## 4    18     313 11.5   62     5   4
## 5    NA      NA 14.3   56     5   5
## 6    28      NA 14.9   66     5   6

dcast를 통해 각 열을 재배치하여 원래 형태의 데이터로 복구한 것을 확인 할 수 있습니다.

이해가 잘 안가신다면 다음의 그림을 보세요:

Figure 1: dcast 함수에 대한 설명입니다. 파란색 음영부분은 우리가 각 행에 표시하고 싶은 “ID variables”를 나타냅니다. 빨간색 음영부분은 전환하고 싶은 열의 이름을 나타냅니다. 회색 음영부분은 각 열을 채울 데이터 값입니다.

cast연습하기

앞서 만들었던 chick_m을 dcast를 사용하여 원래 모양대로 되돌려 출력만 해보세요

##   Time Chick Diet weight
## 1    0    18    1     39
## 2    0    16    1     41
## 3    0    15    1     41
## 4    0    13    1     41
## 5    0     9    1     42
## 6    0    20    1     41

casting을 할 때 우리를 혼란스럽게 만들수도 있는 한가지 실수가 있습니다.

다음을 한번 봐보세요.

dcast(aql, month ~ variable)
## Aggregation function missing: defaulting to length
##   month ozone solar.r wind temp
## 1     5    31      31   31   31
## 2     6    30      30   30   30
## 3     7    31      31   31   31
## 4     8    31      31   31   31
## 5     9    30      30   30   30

만약 해당 코드를 실행했을 경우, 다음과 같은 warning 메시지를 확인 하실 수 있습니다.

# Aggregation function missing: defaulting to length

출력한 것을 확인해보면, 각 달별로 몇개의 행이 있는지 출력되는 것을 볼 수 있습니다. 이상태로 각 cell 별로 여러가지의 값이 있는 데이터를 cast했을 때, 데이터를 aggregate(e.g., sum, mean, etc..)할 필요가 있습니다.

1.2.1. Aggregation

aggregate 함수를 적용하여 cast를 해보도록 하겠습니다. 해당 데이터셋에는 NA값이 존재함으로 NA값을 제거하는 na.rm=T인수를 실행 할수도 있고, 하지 않을 수도 있습니다.

  • 물론 하지 않으면 결과값이 NA로 나온다는 것은 이제 다들 알고 있으리라 믿습니다.
dcast(aql, month ~ variable, fun.aggregate = mean, 
  na.rm = TRUE)
##   month    ozone  solar.r      wind     temp
## 1     5 23.61538 181.2963 11.622581 65.54839
## 2     6 29.44444 190.1667 10.266667 79.10000
## 3     7 59.11538 216.4839  8.941935 83.90323
## 4     8 59.96154 171.8571  8.793548 83.96774
## 5     9 31.44828 167.4333 10.180000 76.90000

Aggregation 연습하기

chick_m을 활용해서 dcast의 aggregation 인수를 사용하여 Diet별로 weight의 평균을 구하여 출력해보세요

##   Diet   weight
## 1    1 102.6455
## 2    2 122.6167
## 3    3 142.9500
## 4    4 135.2627

1.2.2. Margins

Aggregation 적용하여 cast를 수행할 때, 추가로 margins 인수를 적용 할 수 있습니다. margins=T를 주게 되면 cast된 모든 부분에 전부 aggregation 함수를 적용하게 됩니다. 또한 주어진 수식의 순서나 위치를 바꾸고 margins를 적용하게 된다면 해당 ID에 따른 각 열의 값에 aggregation 함수를 적용하게 됩니다.

  • 다음은 그 예제입니다.
#margins = T
# 달별 합계, 총 합계, 각 variable의 합계를 구하
head(dcast(aql, month + day ~ variable, sum, margins = TRUE),32)
##    month   day ozone solar.r  wind temp (all)
## 1      5     1    41     190   7.4   67 305.4
## 2      5     2    36     118   8.0   72 234.0
## 3      5     3    12     149  12.6   74 247.6
## 4      5     4    18     313  11.5   62 404.5
## 5      5     5    NA      NA  14.3   56    NA
## 6      5     6    28      NA  14.9   66    NA
## 7      5     7    23     299   8.6   65 395.6
## 8      5     8    19      99  13.8   59 190.8
## 9      5     9     8      19  20.1   61 108.1
## 10     5    10    NA     194   8.6   69    NA
## 11     5    11     7      NA   6.9   74    NA
## 12     5    12    16     256   9.7   69 350.7
## 13     5    13    11     290   9.2   66 376.2
## 14     5    14    14     274  10.9   68 366.9
## 15     5    15    18      65  13.2   58 154.2
## 16     5    16    14     334  11.5   64 423.5
## 17     5    17    34     307  12.0   66 419.0
## 18     5    18     6      78  18.4   57 159.4
## 19     5    19    30     322  11.5   68 431.5
## 20     5    20    11      44   9.7   62 126.7
## 21     5    21     1       8   9.7   59  77.7
## 22     5    22    11     320  16.6   73 420.6
## 23     5    23     4      25   9.7   61  99.7
## 24     5    24    32      92  12.0   61 197.0
## 25     5    25    NA      66  16.6   57    NA
## 26     5    26    NA     266  14.9   58    NA
## 27     5    27    NA      NA   8.0   57    NA
## 28     5    28    23      13  12.0   67 115.0
## 29     5    29    45     252  14.9   81 392.9
## 30     5    30   115     223   5.7   79 422.7
## 31     5    31    37     279   7.4   76 399.4
## 32     5 (all)    NA      NA 360.3 2032    NA
# month + day ~ variable 식에서 margins="day" 별 sum

head(dcast(aql, month + day ~ variable, sum, margins="day",na.rm=T),32)
##    month   day ozone solar.r  wind temp
## 1      5     1    41     190   7.4   67
## 2      5     2    36     118   8.0   72
## 3      5     3    12     149  12.6   74
## 4      5     4    18     313  11.5   62
## 5      5     5     0       0  14.3   56
## 6      5     6    28       0  14.9   66
## 7      5     7    23     299   8.6   65
## 8      5     8    19      99  13.8   59
## 9      5     9     8      19  20.1   61
## 10     5    10     0     194   8.6   69
## 11     5    11     7       0   6.9   74
## 12     5    12    16     256   9.7   69
## 13     5    13    11     290   9.2   66
## 14     5    14    14     274  10.9   68
## 15     5    15    18      65  13.2   58
## 16     5    16    14     334  11.5   64
## 17     5    17    34     307  12.0   66
## 18     5    18     6      78  18.4   57
## 19     5    19    30     322  11.5   68
## 20     5    20    11      44   9.7   62
## 21     5    21     1       8   9.7   59
## 22     5    22    11     320  16.6   73
## 23     5    23     4      25   9.7   61
## 24     5    24    32      92  12.0   61
## 25     5    25     0      66  16.6   57
## 26     5    26     0     266  14.9   58
## 27     5    27     0       0   8.0   57
## 28     5    28    23      13  12.0   67
## 29     5    29    45     252  14.9   81
## 30     5    30   115     223   5.7   79
## 31     5    31    37     279   7.4   76
## 32     5 (all)   614    4895 360.3 2032
# day + month ~ variable 식에서 margins="month" 별 sum

head(dcast(aql, day + month ~ variable, sum, margins="month",na.rm=T),8)
##   day month ozone solar.r wind temp
## 1   1     5    41     190  7.4   67
## 2   1     6     0     286  8.6   78
## 3   1     7   135     269  4.1   84
## 4   1     8    39      83  6.9   81
## 5   1     9    96     167  6.9   91
## 6   1 (all)   311     995 33.9  401
## 7   2     5    36     118  8.0   72
## 8   2     6     0     287  9.7   74

margins 연습

chick_m을 활용하여 dcast의 aggregation과 margins를 사용하여 Diet별 Time에 따른 weight의 평균을 구하시오

##    Diet  Time    weight
## 1     1     0  41.40000
## 2     1     2  47.25000
## 3     1     4  56.47368
## 4     1     6  66.78947
## 5     1     8  79.68421
## 6     1    10  93.05263
## 7     1    12 108.52632
## 8     1    14 123.38889
## 9     1    16 144.64706
## 10    1    18 158.94118
## 11    1    20 170.41176
## 12    1    21 177.75000
## 13    1 (all) 102.64545
## 14    2     0  40.70000

2. magrittr

magrittr 패키지는 연산자(operator)들의 집합들을 제공합니다.

해당 패키지의 장점은 다음과 같습니다.

  • 데이터 연산을 왼쪽에서 오른쪽 순서로 구조화,
  • nested 함수 호출을 피함,
  • 지역 변수 및 함수의 정의의 필요성을 최소화,
  • 연산 순서 내에서 어디서나 추가 step을 만들 수 있음

저희는 그 중에서 데이터 연산을 왼쪽에서 오른쪽 순서로 구조화하는 부분(main operator)만 다뤄보겠습니다. 해당 부분은 dplyr에서도 설명을 드렸지만 f(x)를 x %>% f()로 대체할 수 있습니다. 이 연산자가 main operator인데 해당 기능이 의미 없이 보이시겠지만 여러가지 기능을 결합할 때 그 이점이 더욱 명확해집니다.

  • 다음은 main operator의 예제입니다.
library(magrittr)

car_data <- 
  mtcars %>%                         #1
  subset(hp > 100) %>%               #2
  aggregate(. ~ cyl, data = ., FUN = . %>% mean %>% round(2)) %>%                        #3
  transform(kpl = mpg %>% multiply_by(0.4251)) %>%  #4
  print                              #5
##   cyl   mpg   disp     hp drat   wt  qsec   vs   am gear carb       kpl
## 1   4 25.90 108.05 111.00 3.94 2.15 17.75 1.00 1.00 4.50 2.00 11.010090
## 2   6 19.74 183.31 122.29 3.59 3.12 17.98 0.57 0.43 3.86 3.43  8.391474
## 3   8 15.10 353.10 209.21 3.23 4.00 16.77 0.00 0.14 3.29 3.50  6.419010

해석을 해보도록 하겠습니다.

  • mtcars 데이터셋을(#1)
  • hp를 기준으로 100보다 큰 데이터만 추출한 후(#2)
  • cyl를 기준으로 각 변수들의 평균을 구한 다음에 소수점 둘째 자리까지 반올림을 한 후(#3)
  • kpl(kilometer per liter) 열을 만들어 mpg*0.4251을 수행하고(#4)
  • 만들어진 데이터를 출력(#5)과 동시에 car_data에 할당하는 과정입니다.

체인을 사용하지 않고 위의 과정을 작성해보겠습니다.

car_data <- 
  transform(aggregate(. ~ cyl, 
                      data = subset(mtcars, hp > 100), 
                      FUN = function(x) round(mean(x),2)), 
            kpl = mpg*0.4251)
car_data
##   cyl   mpg   disp     hp drat   wt  qsec   vs   am gear carb       kpl
## 1   4 25.90 108.05 111.00 3.94 2.15 17.75 1.00 1.00 4.50 2.00 11.010090
## 2   6 19.74 183.31 122.29 3.59 3.12 17.98 0.57 0.43 3.86 3.43  8.391474
## 3   8 15.10 353.10 209.21 3.23 4.00 16.77 0.00 0.14 3.29 3.50  6.419010

어떤 순서로 데이터 처리가 이루어지는지 알기가 힘듭니다.



토막 상식

“.”의 역할에 대해서 알아봅시다. 일반적으로 %>%연산자만 사용하시게 되면 제일 첫 인수에 자동으로 배정이 됩니다

  • e.g.,
iris %>% head(3)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
head(iris,3)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa

하지만 데이터를 넘겨줘야 할 인수의 위치가 첫번째가 아닐 경우 다음과 같은 에러를 확인할 수 있습니다.

  • gsub()는 찾아 바꾸는 함수로써, 사용방법은 gsub(찾을문자나 숫자,바꿀문자나 숫자, 데이터) 입니다.
a<-c("bannananana","an apple")
gsub("n","l",a)
## [1] "ballalalala" "al apple"
a %>% gsub("n","l")
## Warning in gsub(., "n", "l"): 인자 'pattern'는 반드시 길이가 1 보다 커야 하
## 고, 오로지 첫번째 요소만이 사용될 것입니다
## [1] "l"

이러한 상황일 때, “.”을 원하는 위치에 넣어주시면 해당 위치에 데이터가 넘어가는 것을 확인 하실 수 있습니다

gsub("n","l",a)
## [1] "ballalalala" "al apple"
a %>% gsub("n","l",.)
## [1] "ballalalala" "al apple"

“.”은 magrittr나 dplyr에만 속해 있는 것이 아니라 R의 base에 정해진 규칙으로 가끔 수식 같은데에 보는 .~cyl의 사용법과 같습니다.

PR13 과제

  1. 상단에 제공된 소스코드를 그대로 작성해보시오
  2. 상단에 제공된 소스코드 사이에 있는 연습문제를 푸시오
  3. 하단에 제공된 다음의 문제들을 단계별로 푸시오

다음은 2013년 전기판매량 DB 정보입니다.

electro <- read.csv("https://raw.githubusercontent.com/halrequin/data/master/2013%20electronic%20sales.csv",encoding = "utf8",stringsAsFactors = F)
electro
##      년도 월별 발전사업 신재생에너지.사업 구역전기사업 판매량계
## 1  2013년 01월  1007669             19044        80879  1107593
## 2  2013년 02월   896520             20183        58494   975197
## 3  2013년 03월   903324             27337        25720   956382
## 4  2013년 04월   699198             22034        24896   746129
## 5  2013년 05월   359788             31664        34431   425883
## 6  2013년 06월   648183             30766        12542   691492
## 7  2013년 07월   672404             25042        13943   711391
## 8  2013년 08월   692610              5913        17088   715612
## 9  2013년 09월   451409             23978        12205   487593
## 10 2013년 10월   760366             40902        14434   815703
## 11 2013년 11월   931629               107        25797   957535
## 12 2013년 12월   986468             23654        32688  1042812
  1. melt를 사용하여 id는 년도와 월별을, variable은 나머지 행으로 하여 long 포맷 데이터를 만들어 elec_long 변수를 만들어보세요.
##     년도 월별 variable   value
## 1 2013년 01월 발전사업 1007669
## 2 2013년 02월 발전사업  896520
## 3 2013년 03월 발전사업  903324
## 4 2013년 04월 발전사업  699198
## 5 2013년 05월 발전사업  359788
## 6 2013년 06월 발전사업  648183
  1. elec_long 변수를 활용하여 dcast를 적용하여 2013년도 각 열들의 sum을 출력하세요.
##     년도 발전사업 신재생에너지.사업 구역전기사업 판매량계
## 1 2013년  9009568            270624       353117  9633322
  1. elec_long 변수를 활용하여 dcast를 적용하여 2013년도 각 달별로 모든 부분에서의 sum을 출력하세요.
##      년도  월별 발전사업 신재생에너지.사업 구역전기사업 판매량계    (all)
## 1  2013년  01월  1007669             19044        80879  1107593  2215185
## 2  2013년  02월   896520             20183        58494   975197  1950394
## 3  2013년  03월   903324             27337        25720   956382  1912763
## 4  2013년  04월   699198             22034        24896   746129  1492257
## 5  2013년  05월   359788             31664        34431   425883   851766
## 6  2013년  06월   648183             30766        12542   691492  1382983
## 7  2013년  07월   672404             25042        13943   711391  1422780
## 8  2013년  08월   692610              5913        17088   715612  1431223
## 9  2013년  09월   451409             23978        12205   487593   975185
## 10 2013년  10월   760366             40902        14434   815703  1631405
## 11 2013년  11월   931629               107        25797   957535  1915068
## 12 2013년  12월   986468             23654        32688  1042812  2085622
## 13 2013년 (all)  9009568            270624       353117  9633322 19266631
## 14  (all) (all)  9009568            270624       353117  9633322 19266631
  1. elec_long 변수를 활용하여 dcast를 적용하여 원래의 electro의 모양대로 wide 포맷데이터로 변환하여 저장하시오
##      년도 월별 발전사업 신재생에너지.사업 구역전기사업 판매량계
## 1  2013년 01월  1007669             19044        80879  1107593
## 2  2013년 02월   896520             20183        58494   975197
## 3  2013년 03월   903324             27337        25720   956382
## 4  2013년 04월   699198             22034        24896   746129
## 5  2013년 05월   359788             31664        34431   425883
## 6  2013년 06월   648183             30766        12542   691492
## 7  2013년 07월   672404             25042        13943   711391
## 8  2013년 08월   692610              5913        17088   715612
## 9  2013년 09월   451409             23978        12205   487593
## 10 2013년 10월   760366             40902        14434   815703
## 11 2013년 11월   931629               107        25797   957535
## 12 2013년 12월   986468             23654        32688  1042812
  1. 체이닝 연산자를 사용하여 1번과 같은 데이터를 만들고 각 월별 사업 별 막대 그래프를 그리는 것 까지 해보세요. 단, 이때 새로운 변수를 만드시면 안됩니다.(elec_long 사용금지)


Archives

11-01 10:30

Contact Us

Address
경기도 수원시 영통구 원천동 산5번지 아주대학교 다산관 429호

E-mail
textminings@gmail.com

Phone
031-219-2910

Tags

Calendar

«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Copyright © All Rights Reserved
Designed by CMSFactory.NET