04-23 21:40
Notice
Recent Posts
Recent Comments
관리 메뉴

Scientific Computing & Data Science

[Data Science / MongoDB] R과 MongoDB 연동하기 (rmongodb) 본문

Data Science/MongoDB

[Data Science / MongoDB] R과 MongoDB 연동하기 (rmongodb)

cinema4dr12 2014. 6. 3. 16:22

이번 글에서는 MongoDB의 응용으로서 통계 분석의 오픈소스 S/W로 널리 알려진 R에서 MongoDB를 연동하는 방법에 대해 소개하고자 한다.


R용 MongoDB 패키지 설치 및 불러오기

우선 가장 먼저할 일은 R에서 MongoDB를 연동하는 패키지인 rmongodb를 설치하는 것이다:

> install.packages("rmongodb")


설치가 완료되면 라이브러리(패키지)를 불러온다:

> library(rmongodb)


MongoDB 서버 열기

R에서 MongoDB를 연결하기 전에 MongoDB 서버를 작동시킨다. 일단은 Command Line Tool(Mac에서는 Terminal, Winodws에서는 Console)을 이용하여 MongoDB 로컬 서버를 열도록 하겠다.

$ mongod -dbpath /{YOUR_MONGODB_DATA_PATH}/


R에서 MongoDB 연결

다시 R로 돌아와 작동 중인 MongoDB와 연결하기 위해 다음과 같이 입력한다:

> mongo = mongo.create(host = "localhost")

변수 mongo는 연결된 MongoDB의 인스턴스를 갖는다.


연결이 성공적으로 되었는지 확인하려면 다음과 같이 입력한다:

> mongo.is.connected(mongo)
[1] TRUE

결과가 TRUE이면 성공적으로 연결이 되었음을 의미한다.


테스트용 데이터 입력

테스트를 위해 MongoDB 쉘(Mac: Terminal, Windows: Console)을 연결한다:

$ mongo

test라는 이름의 db를 생성하고 users 컬렉션에 다음과 같이 데이터를 삽입한다:

> use test
switched to db test
> db.users.insert({name: "gchoi", age: 37})
> db.users.insert({name: "jmpark", age: 25})
> db.users.find().pretty()
{
	"_id" : ObjectId("538d8cd7c5133151e018bdc9"),
	"name" : "gchoi",
	"age" : 37
}
{
	"_id" : ObjectId("538d8ce1c5133151e018bdca"),
	"name" : "jmpark",
	"age" : 25
}

R에서 현재 등록된 db 리스트를 확인하려면 다음과 같이 입력한다:

> mongo.get.databases(mongo)
[1] "test"

test db에 등록된 컬렉션 리스트를 확인하려면 다음과 같이 입력한다:

> mongo.get.database.collections(mongo, db = "test")
[1] "test.users"


데이터 쿼리

다음의 예는 test db의 users 컬렉션의 데이터를 확인하는 것이다:

> find_all = mongo.find.all(mongo, ns = "test.users")
경고메시지:
In mongo.cursor.to.list(cursor) :
  This fails for most NoSQL data structures. I am working on a new solution
> find_all
    _id name     age
val 0   "gchoi"  37 
val 1   "jmpark" 25

놀랍게도 데이터가 테이블 형식으로 정리되어 출력된다. 옵션 중 ns는 데이터를 검색하고자 하는 네임스페이스(namespace)이다. 형식은 database.collection이 된다.

만약 test.users에서 name이 "gchoi"를 검색하는 쿼리는 다음과 같다:

> tmp = mongo.find.one(mongo, ns = "test.users", list(name="gchoi"))
> tmp
	_id : 7 	 538d8cd7c5133151e018bdc9
	name : 2 	 gchoi
	age : 1 	 37.000000

또는 age가 25인 user를 검색하는 쿼리는 다음과 같다:

> tmp = mongo.find.one(mongo, ns = "test.users", list(age=25))
> tmp
	_id : 7 	 538d8ce1c5133151e018bdca
	name : 2 	 jmpark
	age : 1 	 25.000000


데이터 입력하기

Mongo 쉘에서 test.users 컬렉션에 데이터를 입력하는 방법은 다음과 같다:

> db.users.insert({name: "tjkwak", age: 32L})

동일한 명령을 R에서 수행하려면 다음과 같이 입력한다:

> ns = "test.users"
> buf = mongo.bson.buffer.create()
> mongo.bson.buffer.append(buf, "name", "tjkwak")
[1] TRUE
> mongo.bson.buffer.append(buf, "age", 32L)
[1] TRUE
> b = mongo.bson.from.buffer(buf)
> mongo.insert(mongo, ns, b)
[1] TRUE
> mongo.find.all(mongo, ns)
    _id name     age  
val 1   "gchoi"  37   
val 0   "jmpark" 25   
val 1   "tjkwak" 32

무척이나 복잡해 보인다. 위의 내용을 정리해 보면,

- 네임스페이스(ns), 즉, database.collection을 정의한다.
- bson 버퍼 인스턴스(buf)를 생성한다.
buf에 데이터를 입력한다.
buf로부터 bson 데이터(b)를 생성한다.
- bson 데이터를 입력한다.

그러나, 사실 이 과정을 한 줄의 명령어로 수행할 수 있다:

> mongo.insert(mongo, ns, list(name="hskim", age=37L))
[1] TRUE
> mongo.find.all(mongo, ns)
    _id name     age  
val 1   "gchoi"  37   
val 1   "jmpark" 25   
val NA  "tjkwak" 32
val 1   "hskim"  37


데이터 업데이트

R에서 만약 "gchoi"의 "age"를 37에서 38로 업데이트 하려면 다음과 같이 입력한다:

> mongo.update(mongo, ns, list(name="gchoi"), list(name="gchoi", age=38L))
[1] TRUE


bson 데이터로부터 데이터 가져오기

Mongo 쉘에서 다음과 같이 데이터가 저장되어 있다고 하자:

> db.users.find().pretty()
{
	"_id" : ObjectId("544fa59e75b4d0ef59342106"),
	"name" : "gchoi",
	"age" : 37
}
{
	"_id" : ObjectId("544fa5ad75b4d0ef59342107"),
	"name" : "jmpark",
	"age" : 25
}
{
	"_id" : ObjectId("544fa5f875b4d0ef59342108"),
	"name" : "tjkwak",
	"age" : 32
}
{
	"_id" : ObjectId("544fa62875b4d0ef59342109"),
	"name" : "hskim",
	"age" : 37
}


이제 R에서 저장된 데이터 중 "name"이 "gchoi"인 데이터를 찾기 위해 다음과 같이 입력한다:

> ns = "test.users";
> tmp = mongo.find.one(mongo, ns = ns, list(name="gchoi"));
> tmp
	_id : 7 	 544fa59e75b4d0ef59342106
	name : 2 	 gchoi
	age : 1 	 37.000000

 

tmp는 bson 포맷의 데이터이며, "age" 키값에 대한 필드값을 읽으려면 다음과 같이 입력한다:

> age = mongo.bson.value(tmp, 'age')
> age
[1] 37


CSV 파일로부터 데이터 입력하기

CSV 파일로부터 얻어온 데이터를 일괄적으로 MongoDB에 입력하는 방법에 대해 알아보자. 

우선 다음의 CSV 파일을 다운받는다:

contribution.csv


이 파일을 R의 현재 working directory로 복사(또는 이동)한다.

현재 working directory에서 다음 .R 파일을 작성한다:

mongoTest = function() {
  # Import "rmongodb" package
  library(rmongodb);
  
  # Import CSV file
  don = read.csv("contribution.csv");
  
  # Create mongo instance
  mongo = mongo.create(host = "localhost");
  
  # Assign db & collection (database.collection)
  ns = "test.contribution";
  
  if(mongo.is.connected(mongo)) { # if mongodb is successfully connected
    rows = nrow(don); # Get the total number of data
    
    # For each row read from CSV file and insert it into mongodb
    for(i in 1:rows) {
      mongo.insert(mongo, ns, list(Gender=as.character(don[i,1]), ClassYear=as.numeric(don[i,2]), MaritalStatus=as.character(don[i,3]), Major=as.character(don[i,4]), NextDegree=as.character(don[i,5]), FY04Giving=as.numeric(don[i,6]), FY03Giving=as.numeric(don[i,7]), FY02Giving=as.numeric(don[i,8]), FY01Giving=as.numeric(don[i,9]), FY00Giving=as.numeric(don[i,10]) ) );
    }
    
  }
  
}

파일이름은 어느 것이나 상관없으나 편의상 함수 이름과 동일하게 "mongoTest.R"로 하였다.

R의 console 윈도우에서 mongoTest.R을 로딩한다:

> source('{YOUR_WORKING_DIRECTORY}/mongoTest.R')

R console에서 mongoTest() 함수를 실행한다:

> mongoTest()

Mongo 쉘에서 데이터가 바르게 입력되었는지 확인한다:

> db.contribution.find().pretty()
{
  "_id" : ObjectId("538e7af5a3ce27256fa722d9"),
  "Gender" : "M",
  "ClassYear" : 1957,
  "MaritalStatus" : "M",
  "Major" : "History",
  "NextDegree" : "LLB",
  "FY04Giving" : 2500,
  "FY03Giving" : 2500,
  "FY02Giving" : 1400,
  "FY01Giving" : 12060,
  "FY00Giving" : 12000
}
{
  "_id" : ObjectId("538e7af5a3ce27256fa722da"),
  "Gender" : "M",
  "ClassYear" : 1957,
  "MaritalStatus" : "M",
  "Major" : "Physics",
  "NextDegree" : "MS",
  "FY04Giving" : 5000,
  "FY03Giving" : 5000,
  "FY02Giving" : 5000,
  "FY01Giving" : 5000,
  "FY00Giving" : 10000
}
{
  "_id" : ObjectId("538e7af5a3ce27256fa722db"),
  "Gender" : "F",
  "ClassYear" : 1957,
  "MaritalStatus" : "M",
  "Major" : "Music",
  "NextDegree" : "NONE",
  "FY04Giving" : 5000,
  "FY03Giving" : 5000,
  "FY02Giving" : 5000,
  "FY01Giving" : 5000,
  "FY00Giving" : 10000
}
{
  "_id" : ObjectId("538e7af5a3ce27256fa722dc"),
  "Gender" : "M",
  "ClassYear" : 1957,
  "MaritalStatus" : "M",
  "Major" : "History",
  "NextDegree" : "NONE",
  "FY04Giving" : 0,
  "FY03Giving" : 5100,
  "FY02Giving" : 200,
  "FY01Giving" : 200,
  "FY00Giving" : 0
}
Type "it" for more
>

물론 더 많은 데이터가 출력되겠지만 생략하였다. 명령 it를 입력하면 계속하여 데이터를 출력할 수 있다.

CSV 파일의 데이터 형식이나 컬럼(column) 길이에 따라 작성하는 함수 형태가 조금씩 달라지겠지만 요령은 동일하기 때문에 상황에 맞게 수정하여 쓰면 될 것이다.

다시 역으로 MongoDB에 저장된 데이터를 가져오도록 하겠다. 앞부분에서 언급한 바와 같이 MongoDB 인스턴스(mongo)를 생성하고, mongo.find.all 함수를 이용한다:

> library(rmongodb)
> don = mongo.find.all(mongo, ns = "test.contribution")
다음에 오류가 있습니다mongo.is.connected(mongo) : 객체 'mongo'를 찾을 수 없습니다
> mongo = mongo.create(host = "localhost");
> don = mongo.find.all(mongo, ns = "test.contribution")
경고메시지:
In mongo.cursor.to.list(cursor) :
  This fails for most NoSQL data structures. I am working on a new solution

가져온 데이터를 don이라는 변수에 저장하였는데 데이터 타입(클래스 형식)은 matrix이다:

> class(don)
[1] "matrix"

R에서 데이터를 취급할 때 matrix 보다는 data frame 형식이 편리하기 때문에 클래스 형식을 data frame으로 변환한다:

> don = as.data.frame(don)
> class(don)
[1] "data.frame"

don의 데이터 필드에 대해 살펴보자:

> names(don)
 [1] "_id"           "Gender"        "ClassYear"     "MaritalStatus" "Major"        
 [6] "NextDegree"    "FY04Giving"    "FY03Giving"    "FY02Giving"    "FY01Giving"   
[11] "FY00Giving"

이제 R에서 데이터베이스의 쿼리와 같은 효과로 데이터를 처리할 수 있다. 예를 들어 기부자의 분포를 성별(Gender) 분포를 확인해 보자. 그런데 우선 짚고 넘어가야할 부분이 각 데이터의 타입문제이다. don$Gender의 데이터 형식을 확인해보면:

> class(don$Gender)
[1] "list"

list 형식으로 되어있음을 알 수 있다. 사실 데이터 취급 시 list 형식이 큰 문제는 없을 것으로 보이지만 데이터를 출력해 보면 쓸데 없는 정보가 포함된다:

> don$Gender
$val
[1] "M"

$val
[1] "F"

$val
[1] "F"

$val
[1] "F"

$val
[1] "M"
...

일괄적으로 $val이 나온다. 보다 깔끔한 데이터를 위해 다음과 같이 데이터 형식을 변환한다:

> don$Gender = as.character(don$Gender)
> don$Gender
   [1] "M" "M" "F" "M" "M" "F" "F" "F" "M" "M" "F" "F" "M" "M" "F" "F" "M" "M" "F" "M" "F"
  [22] "F" "M" "M" "F" "M" "F" "M" "F" "M" "M" "F" "M" "F" "M" "F" "M" "M" "M" "M" "M" "M"
  [43] "M" "M" "M" "M" "F" "M" "F" "M" "M" "M" "M" "M" "F" "M" "F" "M" "F" "M" "F" "F" "M"
  [64] "M" "F" "M" "F" "M" "M" "F" "M" "M" "M" "M" "F" "M" "F" "M" "F" "M" "F" "M" "M" "M"
  [85] "M" "F" "M" "M" "F" "M" "F" "M" "F" "M" "F" "M" "M" "F" "M" "F" "F" "F" "M" "M" "M"
 [106] "F" "M" "M" "M" "M" "M" "F" "M" "F" "M" "M" "M" "M" "M" "M" "M" "F" "M" "F" "F" "M"
 [127] "M" "M" "M" "F" "M" "M" "M" "M" "M" "F" "F" "M" "M" "M" "M" "M" "F" "F" "M" "M" "M"
 [148] "F" "M" "F" "M" "F" "M" "F" "M" "F" "M" "F" "M" "F" "F" "M" "M" "F" "M" "M" "F" "M"
 [169] "M" "M" "F" "M" "F" "F" "M" "M" "M" "M" "M" "F" "F" "F" "M" "M" "M" "M" "M" "M" "F"
 [190] "M" "F" "M" "F" "M" "M" "M" "F" "M" "F" "M" "F" "F" "F" "F" "M" "F" "F" "M" "M" "F"
 [211] "M" "M" "M" "F" "M" "F" "M" "M" "M" "F" "M" "F" "F" "F" "F" "M" "F" "F" "F" "F" "F"
 [232] "F" "F" "M" "M" "F" "F" "F" "M" "M" "F" "F" "M" "F" "M" "F" "M" "F" "F" "M" "M" "F"
 ...

보다 깔끔한 형식의 데이터로 정리되었다. 다음과 같이 입력하면 기부자의 성별 분포를 알 수 있다:

> table(don$Gender)

  F   M 
615 615

남녀 모두 615명씩으로 동일하다.

FY04Giving(회계년도 2004년도 기부금액)이 $1000을 초과하는 졸업생을 추출해 보자. 이를 위해서는 마찬가지로 list 형식을 numeric 형식으로 변환한다. 앞서 Gender는 character 형식인데 반해 FY04Giving은 숫자이므로 numeric으로 변환함을 상기하기 바란다.

> don$FY04Giving = as.numeric(don$FY04Giving)

이제 FY04Giving이 $1000을 초과하는 졸업생 데이터를 추출해 보자:

> tmp = don[don$FY04Giving > 1000 , ]
> tmp[1:10,]
            _id Gender ClassYear MaritalStatus              Major NextDegree FY04Giving
val   -83207680      M      1957             M            History        LLB    2500.00
val.1         0      M      1957             M            Physics         MS    5000.00
val.2 -83201656      F      1957             M              Music       NONE    5000.00
val.3 -83200264      M      1957             M            Biology        PHD    1500.00
val.4         0      F      1957             M            English        MLS    1500.00
val.5 -83179992      F      1957             W          Sociology         ME    1500.00
val.6 -41872472      M      1957             M            History       NONE    1500.00
val.7         1      F      1957             M  Chemistry-Zoology         MS    2000.00
val.8        -1      M      1957             M            History        PHD   11505.84
val.9 -83207680      F      1957             M Physical Education         ME    1500.00
      FY03Giving FY02Giving FY01Giving FY00Giving
val         2500       1400      12060      12000
val.1       5000       5000       5000      10000
val.2       5000       5000       5000      10000
val.3       1500       1500       1500       1000
val.4       1500       1500       1500       1000
val.5       1500       1000        500        500
val.6       1500        250        455        200
val.7      10000       1000       1200       1000
val.8      10000      10000          0      20000
val.9       1500       1000       2000       1000
이상으로 이번 글을 마치도록 하겠다.

이번 글에서는 R와 MongoDB를 연동하는 방법에 대해 알아보았으며, 실무에서 R을 사용하다 보면 DB와의 연동이 필수적이기 때문에 유의깊게 살펴볼 가치가 있는 내용이라 생각한다.


Comments