{ "contents" : "#Copyright 2012 The Board of Regents of the University of Wisconsin System.\n#Contributors: Jason Shao, James McCurdy, Enhai Xie, Adam G.W. Halstead, \n#Michael H. Whitney, Nathan DiPiazza, Trey K. Sato and Yury V. Bukhman\n#\n#This file is part of GCAT.\n#\n#GCAT is free software: you can redistribute it and/or modify\n#it under the terms of the GNU Lesser General Public License as published by\n#the Free Software Foundation, either version 3 of the License, or\n#(at your option) any later version.\n#\n#GCAT is distributed in the hope that it will be useful,\n#but WITHOUT ANY WARRANTY; without even the implied warranty of\n#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n#GNU Lesser General Public License for more details.\n#\n#You should have received a copy of the GNU Lesser General Public License \n#along with GCAT. If not, see .\n\n# Notes by Jason\n# 9/07/11\n\n\n########################################################################\n# #\n# Function for loading data from tabular format into an object array #\n# #\n########################################################################\n#' Load tabular data\n#'\n#' This function handles loading data from tabular format (.csv, tab-delimited text or R data frame object)\n#' and returns an array of well objects, each filled with raw Time vs. OD data. \n#' It takes single-plate or multiple-plate format data. For single-plate data, \n#'it calls on the function \\code{gcat.reorganize.single.plate.data} to rearrange the table before creating the output object. \n#'\n#' @param file.name Complete path and file name of a comma-separated values (.csv) file containing growth curve data \n#' in the multiple-plate (long) format. \n#' @param input.data A list of tables representing input files read with \\code{read.table}. Used to save time in cases\n#' of running multiple analyses on the same dataset. If used, the function will ignore \\code{file.name} entirely.\n#' @param load.type .csv by default. \n#' @param plate.laout Specifies the layout of the given plate.\n#' @param single.plate.ID specifies a plate name for a single-plate read. If NULL, this is derived from the file name. \n#' @param blank.value Blank OD measurement for uninoculated wells. By default(NULL), the value of the first OD\n#' measurement in each well is used.\n#' @param add.constant A value for r in the log(OD + r) transformation.\n#' @param plate.nrow The number of rows in the input files.\n#' @param plate.ncol The number of columns in the input files.\n#' @param input.skip.lines specifies a plate name for a single-plate read. If NULL, this is derived from the file name. \n#' @param multi.column.headers The headers of the column when analyzing multiple plates.\n#' @param single.column.headers The headers of the column when analyzing a single plate.\n#' @param layout.sheet.headers The headers of the layout file.\n#' @param silent Surpress all messages.\n#' @param verbose Display all messages when analyzing each well.\n#' \n#' @return A list of well objects.\ngcat.load.data = function(file.name = NULL, load.type = \"csv\", input.data = NULL, single.plate.ID = NULL, \n plate.layout = NULL,plate.nrow = 8, plate.ncol = 12, input.skip.lines = 0,\n multi.column.headers = c(\"Plate.ID\", \"Well\", \"OD\", \"Time\"), single.column.headers = c(\"\",\"A1\"), \n layout.sheet.headers = c(\"Strain\", \"Media Definition\"),\n blank.value = NULL, start.index = 2, single.plate = F, silent = T){\n\n ########################################################################\n # Read from .csv, tab-delimited text file or data frame object #\n ########################################################################\n \n\tif(is.null(input.data)){\n\t\t# Either read from .csv.\n\t\tinput.data = read.csv(file.name, stringsAsFactors=F, skip = input.skip.lines, fileEncoding='UTF-8')\n\n\t\t# Determine single plate name if not specified. \n if (is.null(single.plate.ID)){\n # Split the file name by \".\" and discard the last member (file extension). \n single.plate.ID = strsplit(basename(file.name),\"\\\\.\")[[1]]\n single.plate.ID = paste(single.plate.ID[-length(single.plate.ID)],collapse=\".\")\n }\n\t\t}\n\n # Call to arrange data from a single plate format file\n\tif(single.plate)\n\t\tinput.data = gcat.reorganize.single.plate.data(input.data = input.data, single.column.headers,\n blank.value = blank.value, single.plate.ID = single.plate.ID, plate.nrow = plate.nrow, plate.ncol = plate.ncol, silent=silent)\n\n ########################################################################\n # Search for and standardize column headers in input data #\n ########################################################################\n \n # Go through the specified column headers, determining what their positions are in the \n # input data frame and if any are missing.\n \n # Get names of column headers in input data\n input.colnames = colnames(input.data)\n \n # Create a list denoting the column numbers in input data that match each of the specified column names, \n # and a separate list for any missing columns. \n \n column.matches = c()\n missing.list = NULL\n \n\tfor(i in 1:length(multi.column.headers)){\n\t\tif (multi.column.headers[i] %in% input.colnames)\n\t\t\tcolumn.matches[i] = min(which(input.colnames == multi.column.headers[i]))\n\t\t# Take the first column in input file that matches a specified column header.\n\t\telse{\n\t\t\tmissing.list = c(missing.list, i)\n\t\t}\n\t}\n\n # If any columns are missing, stop and report error with missing column names\n\tif (is.vector(missing.list)){\n\t\tmessage = \"The following columns:\"\n\t\tfor (i in missing.list)\n\t\t\tmessage = paste(message, paste(\" \", multi.column.headers[i]), sep = \"\\n\") \n\t\tstop(message, \"\\n were not found in the data file.\")\n\t\t}\n\n # Reorder and rename the columns, using the list of matching column numbers from above.\n\tinput.data = input.data[,column.matches]\n\tnames(input.data)[1:4] = c(\"Plate.ID\", \"Well\", \"OD\", \"Time\") \n\n # Use 'substring' to split the alphanumeric \"Well\" field into row (letters) and column (numbers)\n\tinput.data$Well.row = substring(input.data$Well, 0,1)\n\tinput.data$Well.col = as.numeric(substring(input.data$Well, 2))\n\n\n ########################################################################\n # Create an array of well objects with the Time and OD data #\n ########################################################################\n #\n # Use the by function to split up the data frame into shorter segments by well (row, column and plate)\n \n\twell.array = by(data = input.data[,c(\"OD\", \"Time\")], \n INDICES = list(input.data$Well.row,input.data$Well.col,input.data$Plate.ID), \n FUN = function(x){data.frame(Time=x$Time, OD=x$OD,stringsAsFactors = F)}, simplify = F)\n\n \n # Then apply the function (found in well.class) to each member to create a well object\n well.array = aapply(well.array,function(x){well(x$Time,x$OD)})\n\n # Differentiate any duplicate plate names in the array's dimnames \n\tnew.dimnames = dimnames(well.array)\n for (i in length(new.dimnames[[3]]):1){\n\t\tif (any(new.dimnames[[3]][-i] == new.dimnames[[3]][i]))\n\t\t\tnew.dimnames[[3]][i] = paste(\"another_\", new.dimnames[[3]][i], sep = \"\")\t\n\t\t}\n\tdimnames(well.array) = new.dimnames\n\t\n # Copy the plate/row/column names found in the dimnames into the array objects themselves (use \"position\" slot)\n\tfor(plate in unique(dimnames(well.array)[[3]])){\n\t\tfor (col in unique(dimnames(well.array)[[2]])){\n\t\t\tfor(row in unique(dimnames(well.array)[[1]])){\n\t\t\t\twell.array[[row,col,plate]]@position = c(plate=plate,row=row,col=col)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n ########################################################################\n # Add plate layout information to well array #\n ########################################################################\n \n # Use the object to add media and strain information to the \"well.info\" slot of each well\n # Also set the value of the parameter in slot \"curve.par\" to T for empty wells. \n ########################################################################\n # Add plate layout information to well array #\n ########################################################################\n \n # Use the object to add media and strain information to the \"well.info\" slot of each well\n # Also set the value of the parameter in slot \"curve.par\" to T for empty wells. \n \n\n # If is not provided, do not add strain information, and assume all wells are inoculated. \n if(is.null(plate.layout)){ \n plate.layout = data.frame(Row=rep(PLATE.LETTERS[1:plate.nrow],plate.ncol),Column=rep(1:plate.ncol,each=plate.nrow),rep(\"Unknown Strain\",96),rep(\"Unknown Media\",96))\n colnames(plate.layout) = c(\"Row\", \"Column\", layout.sheet.headers)\n } \n else\n if(!silent) cat(\"\\n\\t\\tusing plate layout to fill well info.\")\n \n\tfor(plate in unique(dimnames(well.array)[[3]])){\n\t\tfor (col in unique(dimnames(well.array)[[2]])){\n\t\t\tfor(row in unique(dimnames(well.array)[[1]])){\n well = well.array[[row,col,plate]]\n # For each well on each plate, find the corresponding row in . \n # If refers to specific plates, then use those to find the correct row. \n # Otherwise, generalize across all plates. \n if (\"Plate.ID\" %in% names(plate.layout)) \n layout.row.number = which(plate.layout$Column==well@position[\"col\"] & \n plate.layout$Row==well@position[\"row\"] & \n plate.layout$Plate.ID==well@position[\"plate\"] ) \n else \n layout.row.number = which(plate.layout$Column==well@position[\"col\"] & \n plate.layout$Row==well@position[\"row\"])\n \n # Error if either no rows or more than one row matches the well\n if (length(layout.row.number) != 1)\n stop(\"incorrectly formatted plate layout! check names of columns, rows, and plates (if applicable).\")\n \n # Add any additional columns to the well's \"well.info\" slot\n well.info = plate.layout[layout.row.number,!(names(plate.layout) %in% c(\"Row\",\"Column\",\"Plate.ID\",layout.sheet.headers))]\n \n # Fix the column name issue if there is only one additional entry. \n if(length(well.info) == 1){\n well.info = data.frame(well.info,stringsAsFactors=F) \n names(well.info) = names(plate.layout)[!(names(plate.layout) %in% c(\"Row\",\"Column\",\"Plate.ID\",layout.sheet.headers))] \n } \n well@well.info = as.list(well.info)\n \n well@well.info$Strain = plate.layout[layout.row.number, layout.sheet.headers[1]]\n well@well.info$Media = plate.layout[layout.row.number, layout.sheet.headers[2]]\n \n # Set parameter in slot \"curve.par\" accordingly \n well@curve.par$empty.well = (plate.layout$Strain[layout.row.number] == \"Empty\") \n well.array[[row,col,plate]] = well\n\t\t\t\t}\n\t\t\t}\n\t\t}\n \n # Set start index value in each well\n well.array = aapply(well.array, function(x,start.index) { x@start.index = start.index; x }, start.index)\n \n ########################################################################\n # Return values to R #\n ######################################################################## \n # \n\t# Console output if desired, return the completed well array.\n\tif (!silent)\n\t\tcat(\"\\n\\t\", dim(well.array)[[3]], \"plates added to array from\", file.name)\n\treturn(well.array)\n\t}\n\n\n\n########################################################################\n# #\n# Reorganize data from single-plate input format before reading #\n# #\n########################################################################\n#\n# This function reorganizes the data frame from a single-plate format file. \n# input.data - data frame read straight from a single-plate format data file. \n# single.plate.ID - specifies a plate name for a single-plate read, since none is given in the single-plate format\n# The plate will be named Plate_1 unless otherwise specified. \n\ngcat.reorganize.single.plate.data = function(input.data, blank.value = NULL, single.column.headers, single.plate.ID = \"Plate_1\", \n plate.nrow = 8, plate.ncol = 12, silent=T){\n \n ########################################################################\n # Standardize the formatting and search for specified column names #\n ######################################################################## \n # \n # Locate the first and last rows from the table and return errors if not defined \n # Note: this only works if the time column is the first column\n \n\theader.row = min(which(input.data[,1] == single.column.headers[1])) \n if (length(header.row) != 1 | is.infinite(header.row))\n stop(\"could not locate header row in input file!\")\n \n # The last row: where column 2 starts to be blank, or the total number of rows, whichever is smaller \n extra.rows.start = min(which(input.data[-(1:header.row),2] == \"\"), which(is.na(input.data[-(1:header.row),2])), nrow(input.data[-(1:header.row),]))\n if (length(extra.rows.start) != 1 & is.infinite(extra.rows.start)) \n stop(\"could not locate last row in input file!\")\n\n # Use header row to rename the columns, then cut off extra rows (including the ones above header)\n\tnames(input.data) = as.character(unlist(input.data[header.row,]))\n input.data = input.data[(header.row+1):(header.row+extra.rows.start-1),]\n \n # Time column: allow for multiple matches to the name (since it's usually blank) but assume it's the first one\n\tTime.column = which(names(input.data) == single.column.headers[1])\n\tif (length(Time.column) != 1){\n if(!silent) cat(\"No unique time column in input.data file! Using the first one encountered.\")\n\t\tTime.column = min(Time.column)\n\t\t}\n # First well column (default A1): only allow for one match.\t\n\tWell.column.start = which(names(input.data) == single.column.headers[2])\n\tif (length(Well.column.start) != 1)\n\t\tstop(\"No unique start point for well columns in input.data file!\")\n\n # If the time column was found, rename it \"Time\" and reformat it into a numeric value\n # Adjust the blank measurement timestamp to -1 seconds if there is one\n\n names(input.data)[Time.column] = \"Time\"\n \n # Note: Some single plate screens have timepoints ending with \"s\" for seconds. \n # This line removes the \"s\" while maintaining general compatibility. \n\tinput.data$Time = unlist(strsplit(input.data$Time, \"s\"))\n\n # If is NULL (default - takes the first OD as the blank reading), then the first timepoint can labeled something non-numeric. \n # In that case, rename it to match the first real timepoint minus one. \n # when user input blank value, Blank timepoint i.e. input.data$Time[1] == Blank, labeled as \"Blank\" from data input file\n # It also should rename it to match the first real timepoint minus one. \n if(is.null(blank.value) || is.na(as.numeric(input.data$Time[1])))\n input.data$Time[1] = as.numeric(input.data$Time[2]) - 1\n \n ########################################################################\n # Start to fill the reformatted data frame #\n ######################################################################## \n \n # If all columns are present, make a list of all the wells.\n\twell.list = paste(rep(PLATE.LETTERS[1:plate.nrow], each = plate.ncol), rep(formatC(1:plate.ncol, digits = log(plate.ncol, 10), flag = \"0\"), plate.nrow), sep = \"\")\n\n #\tDuplicate the well names times the number of time measurements in each well\t\n Well = rep(well.list, each = length(input.data[,Time.column]))\t\n\t\t\n # Duplicate times the length of the entire output \n Plate.ID = rep(single.plate.ID, length(Well))\n\n # Duplicate the time column times the number of wells and convert to numeric\n\tTime = as.numeric(rep(input.data[,Time.column], times = length(Well.column.start:ncol(input.data))))\n\n # Append OD measurements from each well together and convert to numeric\n\tOD = c()\n\tfor (i in Well.column.start:ncol(input.data)){\n\t\tOD = as.numeric(c(OD, input.data[,i]))\n\t\tOD = unlist(OD)\n\t\t}\n\n # Fill and return the data frame containing the above four vectors.\n\toutput = data.frame(Plate.ID, Well, OD, Time)\t\n\t\n # Include any extra columns that were not Time or OD measurements?\n\tfor(i in (1:length(names(input.data)))[-c(Time.column,Well.column.start:ncol(input.data))]){\n\t\tnew.column = data.frame(rep(input.data[,i], length(Well.column.start:ncol(input.data))))\n\t\tnames(new.column) = names(input.data)[i]\n\t\toutput = cbind(output, new.column)\n\t\t}\t\n\treturn(output)\n}\n\n\n########################################################################\n# #\n# Function to combine two well array datasets by plate #\n# #\n########################################################################\n# ----------------------------------------------------------\n# This function can append together arrays created using \n# Arguments: any number of array objects as long as they are all output straight from \n\ngcat.append.arrays = function(...){\n\n # Transfer arrays to a list\n\targs.arrays = list(...)\n\tfirst.array = args.arrays[[1]]\n\tfirst.dims = dim(first.array)\n plate.nrow = args.arrays[[4]]\n plate.ncol = args.arrays[[3]]\n input.arrays = list(args.arrays[[1]], args.arrays[[2]])\n\tfor (i in 2:length(input.arrays)){\n\t\tnext.array = input.arrays[[i]]\n\t\tnext.dims = dim(next.array)\n\t\n # Check to make sure the arrays have proper dimensions for 96-well plate data\n if (!(all(c(first.dims[1], next.dims[1]) == plate.nrow) & all(c(first.dims[2], next.dims[2]) == plate.ncol)))\n\t\t\tstop(\"Data should have dimensions (\",plate.nrow,\",\",plate.ncol,\",n)!\")\n\t\t\n # If dimensions are alright, append dimensions and dimension names\t\n new.dims = c(plate.nrow,plate.ncol,first.dims[3]+next.dims[3])\n\t\tnew.names = dimnames(first.array)\n\t\tnew.names[[3]] = c(new.names[[3]], dimnames(next.array)[[3]]) \n\n # Differentiate duplicate names\n\t\tfor (i in length(new.names[[3]]):1){\n\t\t\tif (any(new.names[[3]][-i] == new.names[[3]][i]))\n\t\t\t\tnew.names[[3]][i] = paste(\"another_\", new.names[[3]][i], sep = \"\")\n\t\t\t}\n\t\n\t\t# Create a new array\n new.array = c(first.array, next.array)\n\t\tdim(new.array) = new.dims\n\t\tdimnames(new.array) = new.names\n\t\t\n # Update plate name in well objects\n\t\tfor (i in 1:length(unlist(new.array)))\n\t\t\tnew.array[[i]]@position[1] = new.names[[3]][floor((i-1)/96)+1]\n\t\t\n\t\t# Loop until complete\n\t\tfirst.array = new.array\n\t\tfirst.dims = dim(first.array)\n\t\t}\n\treturn(new.array)\n\t}\n\n\n########################################################################\n# #\n# Convert timestamps to hours from start and sort timepoints #\n# #\n########################################################################\n#\n# This function acts on a single well and modifies the raw data stored in slot \"screen.data\"\n# \n# input.well - an object of class well\n# time.format - specifies the time format. allowed values are \"%S\", for seconds, \"%d\", for days, or anything complying with ISO C / POSIX standards; see \n# note: reading directly from excel to R results in timestamps being converted to days.\n# start.index - which timepoint should be used as the starting time at inoculation?\n\ngcat.start.times = function(input.well, time.input, start.index = 2) { \n \n if(start.index > length(input.well))\n stop(\"Start index is greater than length of well!\")\n \n # If using a numeric time format, simply multiply times by the appropriate conversion factor\n\t# Conversion factor should be supplied to convert timestamps to hours. For example, \n # should be equal to 1/3600 if \"time\" is in seconds, 24 if \"time\" is in days, etc.\n\n time.format = time.input # Set default constant from rails user input\n \n if (is.numeric(time.format))\n\t\tinput.well@screen.data$Time = (input.well@screen.data$Time - min(input.well@screen.data$Time)) * time.format\n else{\n # Otherwise, convert timestamps from ISO C / POSIX to numeric values representing seconds (since the dawn of time?) and subtract out the initial value. \n\t\trtimes = input.well@screen.data$Time\n ptimes = strptime(as.character(rtimes),time.format)\n\t\tctimes = as.POSIXct(ptimes)\n\t\tint.times = unclass(ctimes)\n # Time will be in seconds, convert to hours by dividing by 3600\n\t\tinput.well@screen.data$Time = (int.times - min(int.times))/3600\n\t\t}\n\t# Sort raw data by timestamp and return the input.well\n\tinput.well@screen.data = input.well@screen.data[order(input.well@screen.data$Time),]\n \n input.well@screen.data$Time = input.well@screen.data$Time - input.well@screen.data$Time[start.index]\n \n if(all(is.na(input.well@screen.data$Time)))\n stop(\"Error in .\") \n rownames(input.well@screen.data) = NULL\n\treturn(input.well)\n }\t\n", "created" : 1425413240737.000, "dirty" : false, "encoding" : "UTF-8", "folds" : "", "hash" : "2796681086", "id" : "2FF709FD", "lastKnownWriteTime" : 1428438949, "path" : "~/Documents/GCAT4/trunk/R/GCAT/R/table2well.R", "project_path" : "R/table2well.R", "properties" : { }, "source_on_save" : false, "type" : "r_source" }