まずは以下の図の要領で生データを積分と標準の方法の二通りのやり方で変換します。
いまのところ、osaka_dataというリストにすべてのエコプレートのデータが複数のサンプルごちゃまぜで格納されています。これをサンプルごとに分けないと、上図の積分や標準の方法へとデータを変換できません。meta_testにはsample_IDというサンプルを区別する情報が「整数(integer)」として格納されています。
#We need to separate the data set into each sample with multiple measurementsclass(meta_test$sample_ID[3]実際にサンプル数がいくつあるかはmeta_testの中身を見れば分かるわけですが、以下の二つの方法で確認するほうがよいです。
#number of samplesmax(meta_test$sample_ID)#ORlength(levels(meta_test$date_time_sample))次にリストを作り直して、sample_IDごとに吸光度の測定回数分だけ存在する複数のデータを整理したいと思います。実はリストは基本的に何でもありなので、リストの要素をさらにリストにすることもできます。以下のような方法をとれば、二次元マトリクスのようにリストを扱うことができ、マトリクスの一つ一つの要素にひとつの観測結果(96個の値)を格納することができます。
#trial to generate the list of listtest_list<-list()test_list[[1]]<-list()test_list[[1]][[1]]<-osaka_data[[1]]test_list[[1]][[2]]<-osaka_data[[2]]test_list[[1]]test_list[[1]][[2]]このトライアルを元に、sample_listというリストを作って、sample_list[[サンプルID]][[k]]という形式でサンプルIDごとにk番目の観測結果を格納することにします。
#separating the data to sample ID-specific listsample_list<-list()no_sample<-max(meta_test$sample_ID)for(i in 1:no_sample) { sample_list[[i]]<-list()}同様にメタデータもサンプルIDごとに分割したいのですが、今度はリストの各要素はリストではなくデータフレームにすればよいです。そのためには以下のようにリストを用意します。
#summarizing the metadata into sample-ID-specific listmetadata_list<-list()no_sample<-max(meta_test$sample_ID)for(i in 1:no_sample) { metadata_list[[i]]<-as.data.frame(meta_test[1,])}さて、各サンプルごとに観測回数が何回分あるかは、メタデータでそれぞれのsample_IDのものがいくつあるか数えればよく、たとえばsample_IDが5のものは以下のように確認できます。
#Check the number of repeated measurementssum(meta_test$sample_ID==5)それでは以上の準備を踏まえて、以下のような二重ループを用意すれば、エコプレート観測データおよびメタデータをサンプルIDごとに整理できます。
i<-1 #initializationfor(j in 1: no_sample) { no_measurement<-sum(meta_test$sample_ID==j) #check the number of repeated measurements #loop for loading all measurement results from sample index j for(k in 1:no_measurement) { sample_list[[j]][[k]]<-osaka_data[[i]] #copy the data of each measurement into sample_list[[sample_index]] metadata_list[[j]][k,]<-meta_test[i,] #copy the metadata i<-i+1 #update the index of the raw data (list osaka_data) }}この結果がどうなっているかは、たとえばサンプルID3の中身を以下のように確かめれば分かるでしょう。
#examplesample_id<-3sample_list[[sample_id]]metadata_list[[sample_id]]以上のスクリプトによって作成したデータリストsample_listとメタデータリストmetadata_listを入力パラメータとするような、積分を自動で行うための関数を以下のように準備します。最初の数行は関数の定義情報です。この関数はecop_series, metadata, min_interval_hourの3つのパラメータがあり、アウトプットはデータフレームとなります。アウトプットのデータフレームにはひとつのサンプルからの積分値が格納されることになります。
##########[2] Definitions of functions to data arrangement (integration, maximum, average, minimum, etc)###################The function for taking integration (=calculation of the area below the color development curve) even when there are gaps in measurement dates##########################list of parameters#ecop_series: data source of ecopalte color patterns from the single sample ID (list format)#metadata: metadata, we need the information of measured time#min_interval_hour, the minimum interval hour, here 24 hours##################Output is a dataframeinteg_ecoplate <- function(ecop_series, metadata, min_interval_hour=24){次の数行は、観測間隔時間(min_interval_hour)を使って観測回数、観測期間等を計算するスクリプトです。 最初の行はサンプルひとつにつき複数ある観測データを格納するためのリストの準備です。sampled_indexはメタデータリストに含まれている培養開始後何時間目に吸光度を測定したかという情報(metadata$measured_time)を何日目かという情報を変換したものです。end_sampleはsampled_indexの最大値なので何日目まで観測したかがこれでわかります。注意しないといけないのは、length_periodは実際の観測回数ですが、これはend_sampleよりも少なくなる可能性があります。なぜなら培養開始6日後(144時間後)が最終観測だとしても、3、4日後には観測をしていなければ、end_sampleの値は6になる一方、length_periodは3になります。
data_eco <- list() #prepare an empty list sampled_index <- metadata$measured_time/min_interval_hour #convert hours to n-th observation (n-th day) end_sample <- max(sampled_index) #The final date of the measurement length_period <- length(ecop_series) #size of the samples, generally less or equal to end_sample, because the interval of the observation is ofen longer than min_interval_hour次の1行では、ecop_seriesに格納されている複数観測日のデータをdata_ecoにコピーします。このときも、sample_indexは{1,2,5,6}の4つの値しかないので,data_eco[3], data_eco[4]は空(NULL)のままになります。
for(i in 1: length_period) data_eco[[sampled_index[i]]] <-ecop_series[[i]] #load the ecoplate data only for sampled dates, some of the element of data_eco can be empty (NULL)次の二行では、data_integというデータフレームを用意して、そこに最初のデータを格納します。
data_integ <- data.frame() #prepare a data framedata_integ <- data_eco[[1]] #Note that "data_eco" should be a list; each item is the information of color development from each measurement date.さて、それ以降のiに関するforループの中身が実際の積分プロセスに相当するのですが、観測間隔がまばらな場合(たとえば、3,4日後にデータがない場合)を想定しています。 実際の計算は複雑な積分近似などせず、以下の図で示すような計算になっています。
上のグラフは96ウェルあるマイクロプレートのうち、ひとつのウェルの吸光度を表していると思ってください。実際に観測地があるのは、24,48,120,144時間後の4つです。以下のスクリプトでは、forループのインデックスiが1から6に向かって一つずつ増えていきますが、iが3になったとき(上のグラフの72時間後に対応)、data_eco[[3]]にはデータがありません(NULL)。この判定はif(is.null(data_eco[[i]])) の条件判定で可能です。is.null()が真のときは、次のjのループによって、データが存在する120時間後(j=5)のところまで探索していきます。j=5になったところで、(!is.null(data_eco[[j]])が真になるので、太字で書かれているkのループを用いて、欠損データを補完します。補完の仕方は上の図にあるように48時間後の値と120時間後の値を直線でつなぐ簡単なものとしました。
for(i in 2:end_sample) { #first check the NULL dates (lack in measurement) if(is.null(data_eco[[i]])) { for(j in (i+1):end_sample) { #search for all NULL dates until non-Null date if(!is.null(data_eco[[j]])) { #when encountring the non-Null date (j) for(k in i: (j-1)) data_eco[[k]] = ((j - k)*data_eco[[i-1]] + (k - i + 1)*data_eco[[j]])/(j - i + 1) #use linear line of two non-NULL dates (i-1 & j) break #skip further loop when non-zero data is found }#end of if data_eco[[j]] }#end of for j }#end of if data_eco[[i]] このようにして空のdata_eco[[k]]に値を入れた後、以下のように単純に補完値も含めた一日後との値を単純に足していきます。これは、上の図の灰色の棒グラフの総面積を計算するという、積分近似です。もっとちゃんとした近似も用意できると思うのですが今はこれで良しとします。
data_integ <- data_integ + data_eco[[i]] #each element of data_eco[[i]] was individually added. }#end of for loop i最後に観測期間で積分値を割ることで、観測期間が異なるサンプル間のばらつきを補正します。
data_integ/(end_sample*min_interval_hour) #output, take the average value over the integration period, i.e., the normalization by integration period}このように関数を定義すれば、一つのサンプルについては例えば次のようにコンソールで実行結果を確認できます。
> sample_id<-4> integ_ecoplate(ecop_series=sample_list[[sample_id]], metadata_list[[sample_id]], min_interval_hour=24) V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 V11 V12A 0.003101190 0.003029762 0.005258929 0.009366071 0.013357143 0.003461310 0.006866071 0.012779762 0.002916667 0.004008929 0.005410714 0.009952381B 0.013651786 0.013860119 0.008663690 0.013125000 0.014080357 0.032985119 0.008386905 0.024922619 0.013059524 0.014000000 0.009032738 0.021288690C 0.013592262 0.003193452 0.002702381 0.004514881 0.014809524 0.003488095 0.002398810 0.003205357 0.012190476 0.003354167 0.002455357 0.003413690D 0.009056548 0.005023810 0.006800595 0.016619048 0.008199405 0.004750000 0.005482143 0.012154762 0.007809524 0.007529762 0.015559524 0.016934524E 0.014101190 0.012833333 0.004172619 0.003681548 0.014827381 0.015178571 0.003818452 0.003779762 0.013169643 0.017023810 0.003514881 0.004056548F 0.022017857 0.004964286 0.005997024 0.010467262 0.027886905 0.003592262 0.003937500 0.008949405 0.017943452 0.003363095 0.004735119 0.014077381H 0.004181548 0.003544643 0.004979167 0.004633929 0.003872024 0.003217262 0.005169643 0.006580357 0.003767857 0.003535714 0.005550595 0.004443452G 0.003776786 0.005199405 0.005005952 0.009931548 0.003166667 0.005229167 0.004812500 0.006276786 0.002913690 0.005029762 0.003267857 0.011389881> それでは、上で作成したデータリストsample_listとメタデータリストmetadata_listのそれぞれの要素を呼び出して積分値を計算する仮定をループの中に入れて以下のようにすべてのサンプルの積分値を取りましょう。
#integrating all samplesintegrated_osaka<-list() #prepare emplty listno_sample<-max(meta_test$sample_ID)for(i in 1:no_sample) integrated_osaka[[i]] <- integ_ecoplate(ecop_series=sample_list[[i]], metadata_list[[i]], min_interval_hour=24)integrated_osaka次に各サンプル、観測最終日のデータだけを使う標準的方法に則ってデータを抽出しましょう。これは非常に簡単で、サンプルごとにsample_list[[サンプルID]][[k]]の最後のkにlength(sample_list[[サンプルID]])で簡単にアクセスできるので、以下のようにすればよいだけです。
#extracting only the final day measurement data#examplesample_list[[1]][[length(sample_list[[1]])]]finalday_osaka<-list() #prepare emplty listno_sample<-max(meta_test$sample_ID)for(i in 1:no_sample) finalday_osaka[[i]] <-sample_list[[i]][[length(sample_list[[i]])]]finalday_osaka以上で生データの変換の第一段階は終わりです。
それでは、生データ変換の第二段階です。ここでは、それぞれの基質ごとに3つある繰り返し値の処理を行います。次の図にあるように、コントロール(ブランク)ウェルを含めてプレート内3繰り返しについて、最大値、平均値、最小値の3つの処理を施します。
これらの処理には、ひとつのプレートからのデータがA-Gの8行・V1-V12の12列のデータフレームに規則的に格納されていること、V1-V4,V4-V8,V9-V12が3つの繰り返し一つ一つに対応することに注目します。(V1,V5,V9)、(V2,V6,V10)(V3,V7,V11)、(V4,V8,V12)のそれぞれのグループで最大・平均・最小について計算すればよいことになります。
平均が一番簡単で、それぞれの列を指定すればそれぞれの行ごとに計算してくれるので、以下の感じで平均が取れます。
data_ave1<-(data_f$V1+data_f$V5+data_f$V9)/3.0 この計算を各グループごとに行って最後にappendという関数を繰り返し使えば96個の値を32個の平均値へと変換できます。関数は以下のようになります。ちなみに太字部分data_sum_nor<-data_sum - data_sum[1] #normalizing by water wellはコントロール(ブランク)の値との差をとることですべての基質の吸光度の標準化過程になっています。
#The function for averaging triplicate#The parameter is data frame#Output is a vectorave_ecoplate <- function(data_f){ data_ave1<-(data_f$V1+data_f$V5+data_f$V9)/3.0 #take average data_ave2<-(data_f$V2+data_f$V6+data_f$V10)/3.0 data_ave3<-(data_f$V3+data_f$V7+data_f$V11)/3.0 data_ave4<-(data_f$V4+data_f$V8+data_f$V12)/3.0 data_sum<-append(append(append(data_ave1,data_ave2),data_ave3),data_ave4) #combine data data_sum_nor<-data_sum - data_sum[1] #normalizing by water well data_sum_nor #output}これは以下のように使うことができます。出力された値はもはやデータフレームではなく、一行のベクトルとなっています。
> sample_id<-2> ave_ecoplate(finalday_osaka[[sample_id]]) [1] 0.000000000 0.466666667 0.688333333 0.035000000 0.837666667 0.087666667 0.541333333 -0.333000000 -0.191666667 -0.324333333 -0.346333333 -0.072666667[13] 0.784666667 -0.161333333 -0.342000000 -0.174333333 0.312666667 0.323333333 -0.372666667 -0.268666667 -0.314333333 -0.009666667 -0.350333333 -0.305000000[25] -0.114333333 0.845333333 -0.329000000 0.524000000 -0.327333333 0.125666667 -0.269333333 -0.090333333次に、最大値と最小値については同一の基質に対応する値を3つとって指定する必要(たとえば、max(data_f$V1[1],data_f$V5[1], data_f$V9[1]))がありますが、簡単に以下のように定義できます。
#The function for taking maximum of triplicate#The parameter is data frame#Output is a vectormax_ecoplate <- function(data_f){ data_max<-max(data_f$V1[1],data_f$V5[1], data_f$V9[1]) for(i in 2:8) data_max <-append(data_max, max(data_f$V1[i], data_f$V5[i], data_f$V9[i])) for(i in 1:8) data_max <-append(data_max, max(data_f$V2[i], data_f$V6[i], data_f$V10[i])) for(i in 1:8) data_max <-append(data_max, max(data_f$V3[i], data_f$V7[i], data_f$V11[i])) for(i in 1:8) data_max <-append(data_max, max(data_f$V4[i], data_f$V8[i], data_f$V12[i])) data_max_nor <- data_max - data_max[1] #normalizing by water well data_max_nor #output}#The function for takign minumu of triplicate#The parameter is data frame#Output is a vectormin_ecoplate <- function(data_f){ data_min<-min(data_f$V1[1], data_f$V5[1], data_f$V9[1]) for(i in 2:8) data_min <-append(data_min, min(data_f$V1[i], data_f$V5[i], data_f$V9[i])) for(i in 1:8) data_min <-append(data_min, min(data_f$V2[i], data_f$V6[i], data_f$V10[i])) for(i in 1:8) data_min <-append(data_min, min(data_f$V3[i], data_f$V7[i], data_f$V11[i])) for(i in 1:8) data_min <-append(data_min, min(data_f$V4[i], data_f$V8[i], data_f$V12[i])) data_min_nor <- data_min - data_min[1] #normalizing by water well data_min_nor #output}それではintegrated_osaka, finalday_osakaに格納されたそれぞれのデータに平均・最大・最小の処理を施し、データフレームにまとめて、ローカルフォルダにもcsvファイルとして保存しましょう。
積分した値の基質ごとの平均値を取るには以下のようにループを使えば可能です。
#Integration + averageosaka_summary_integ_ave<-ave_ecoplate(integrated_osaka[[1]])for(i in 2: length(integrated_osaka)) osaka_summary_integ_ave<-rbind(osaka_summary_integ_ave,ave_ecoplate(integrated_osaka[[i]]))View(osaka_summary_integ_ave)同様に積分値からの最大・最小、最終日データからの平均・最大・最小も同じように処理できます。
#Integration + maxosaka_summary_integ_max<-max_ecoplate(integrated_osaka[[1]])for(i in 2: length(integrated_osaka)) osaka_summary_integ_max<-rbind(osaka_summary_integ_max,max_ecoplate(integrated_osaka[[i]]))#Integration + minosaka_summary_integ_min<-min_ecoplate(integrated_osaka[[1]])for(i in 2: length(integrated_osaka)) osaka_summary_integ_min<-rbind(osaka_summary_integ_min,min_ecoplate(integrated_osaka[[i]]))#finalday + averageosaka_summary_final_ave<-ave_ecoplate(finalday_osaka[[1]])for(i in 2: length(finalday_osaka)) osaka_summary_final_ave<-rbind(osaka_summary_final_ave,ave_ecoplate(finalday_osaka[[i]]))#finalday + maxosaka_summary_final_max<-max_ecoplate(finalday_osaka[[1]])for(i in 2: length(finalday_osaka)) osaka_summary_final_max<-rbind(osaka_summary_final_max,max_ecoplate(finalday_osaka[[i]]))#finalday + minosaka_summary_final_min<-min_ecoplate(finalday_osaka[[1]])for(i in 2: length(finalday_osaka)) osaka_summary_final_min<-rbind(osaka_summary_final_min,min_ecoplate(finalday_osaka[[i]]))これらの処理によってデータをまとめることができるのですが、まだデータの形式がmatrixとなっている(class(osaka_summary_final_ave))ので、以降の多変量解析に使うことを想定してデータフレームへと変換します。このとき、コントロールの値(すでに0に標準化されている)はosaka_summary_integ_ave[,-1]によって削除します。
#convert them into dataframe, delete the control value (0)class(osaka_summary_final_ave)osaka_summary_integ_ave<-as.data.frame(osaka_summary_integ_ave[,-1])osaka_summary_integ_max<-as.data.frame(osaka_summary_integ_max[,-1])osaka_summary_integ_min<-as.data.frame(osaka_summary_integ_min[,-1])osaka_summary_final_ave<-as.data.frame(osaka_summary_final_ave[,-1])osaka_summary_final_max<-as.data.frame(osaka_summary_final_max[,-1])osaka_summary_final_min<-as.data.frame(osaka_summary_final_min[,-1])メタファイルについても上のデータフレームと対応した形で後ほど多変量解析で使いたいので以下のように変換します。
#prepare the meta data file again for sample_IDmetadata_osaka<-metadata_list[[1]][1,1:3]for(i in 1:length(metadata_list)) metadata_osaka<-rbind(metadata_osaka,metadata_list[[i]][1,1:3])metadata_osaka<-as.data.frame(metadata_osaka)以上の結果はそれぞれwrite.csv()という関数を使うと指定したパスのフォルダにcsvファイルとして保存することができます。
#write the result into csv fileswrite.csv(metadata_osaka, "./metadata_osaka.csv", row.names=F, quote=F)後々使うことも考えて以下のように全部保存しておきます。
write.csv(osaka_summary_integ_ave, "./osaka_summary_integ_ave.csv", row.names=F, quote=F)write.csv(osaka_summary_integ_max, "./osaka_summary_integ_max.csv", row.names=F, quote=F)write.csv(osaka_summary_integ_min, "./osaka_summary_integ_min.csv", row.names=F, quote=F)write.csv(osaka_summary_final_ave, "./osaka_summary_final_ave.csv", row.names=F, quote=F)write.csv(osaka_summary_final_max, "./osaka_summary_final_max.csv", row.names=F, quote=F)write.csv(osaka_summary_final_min, "./osaka_summary_final_min.csv", row.names=F, quote=F)これで生データの変換は終了です。上の図にあるようなfunctional matrix (データフレームの形式ですが)が積分(平均・最大・最小)、最終(平均・最大・最小)のそれぞれの処理で得られたことになります。