16 lines
20 KiB
Plaintext
16 lines
20 KiB
Plaintext
{
|
|
"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 <http://www.gnu.org/licenses/>.\n\n########################################################################\n# #\n# Estimate the growth curve slope at each timepoint of a well #\n# #\n########################################################################\n#\n# uses the functions <data.from> and <well.name> (see well.class.R) \n# adds estimated slopes as a new column to the \"screen.data\" slot\n\ncalculate.slopes = function(input.well, silent = T){\n # Get the growth curve data (excluding removed points, but not excluding points marked as tanking)\n\tgrowth.data = data.from(input.well, remove = T, remove.tanking = F)\n\tx = growth.data[,1]\n\ty = growth.data[,2]\n\t\n\t# Get a list of timepoint indices \n\tindices = as.numeric(rownames(growth.data))\n\t\n # Default slope is NA, values will be filled in as they are calculated\n slopes = rep(NA, length(input.well))\n\n\tfor (i in 1:length(x)){ \n\t\tif (i == 1)\n\t\t\tslopes[indices[i]] = NA\n # Calculate the slope of the line drawn from each point to the point preceding it. \n\t\telse\n\t\t\tslopes[indices[i]] = (y[i] - y[i-1])/(x[i] - x[i-1])\n\t\t}\n # Add a Slope column to the \"screen.data\" slot \n\tinput.well@screen.data$Slope = slopes\n\tif (!silent)\n\t\tcat(\"slopes filled for\", input.well@position[1], well.name(input.well), \"\\n\")\n\treturn(input.well)\n }\n \n \n########################################################################\n# #\n# Use slope estimates to check growth curves for tanking and OD jumps #\n# #\n########################################################################\n#\n# uses the functions <data.from> and <well.name> (see well.class.R) \n# Arguments: \n# ----- stringency parameters ----\n# remove.jumps - should the program remove OD jumps? default F (just report them) - \n# should be set to T if data contains distinct jumps in OD that need to be eliminated \n# otherwise, this might be too stringent and will result in loss of data. \n# check.start - which timepoint should checking for jumps and tanking start at? this is included because early timepoints can be unstable.\n# fall.cutoff - what downward slope should constitute a fall in OD? \n# jump.cutoffs - multipliers to determine whether a curve jumps up or down (see methods 1 and 2, below)\n# tank.limit - how many timepoints in a row can have falling slopes until the curve is marked as tanking?\n# tank.cutoff - what proportion of the maximum OD can the curve go below until it is considered tanking?\n\n# ---- input/output ----\n# silent - output to R console?\n# draw - plot growth curve with curve checking details? \n#\n# Fills the \"curve.par\" slot in the well with the starting index of tanking (NA if none is found)\n# \n\n#check.slopes = function(input.well, check.start = 8, fall.cutoff = -.0025, remove.jumps = F,\n#\t\t\tjump.multipliers = -c(15, 500, 10), tank.cutoff = 1.0, tank.limit = 3, silent = T, draw = T){\n\n#changed default values to parameters to account for settling\ncheck.slopes = function(input.well, check.start = 22, fall.cutoff = -.0025, remove.jumps = F,\n\t\t\tjump.multipliers = -c(15, 500, 10), tank.cutoff = 1.0, tank.limit = 6, silent = T, draw = T){\n\n if (!silent)\n cat(\"\\nNow checking slopes for\", plate.name(input.well), well.name(input.well))\n\n\t# Get estimated slopes and untransformed points from the well \n slopes = input.well@screen.data$Slope\n\tx = data.from(input.well, remove=F, remove.tanking=F)[,1]\n\ty = data.from(input.well, remove=F, remove.tanking=F)[,2] \n\t\n\t# Get a list of indices with valid slope estimates\n\tindices = (1:length(input.well))[!is.na(slopes)]\n\n # Do not report tanking or check for jumps if there are fewer valid points than the number needed to detect it\n\tif (length(indices) < tank.limit){\n\t\tinput.well@curve.par$tanking.start = NA\n\t\tif (!silent)\n\t\t\tcat(\"...does not have enough points to check.\\n\")\n\t\treturn(input.well)\n\t}\n\t\n #######################################################################################\n # Create indicator variables and recalculate cutoffs based on timepoint density. #\n #######################################################################################\n #\n\n # Create <slope.indicator>, a vector of indicator variables for each timepoint with a valid slope estimate. \n\tslope.indicator = rep(NA, length(slopes))\n\t\n\t# Calculate the mean time difference between two timepoints (this typically doesn't vary too much)\n time.diff = mean(diff(x[-1]))\n \n\t# Use the mean time difference to recalculate what should constitute a fall in OD using <fall.cutoff> (which should be a proportion)\n\t# Honestly I don't remember why the fifth root thing is in here...this is probably going to be revised later. \n fall.cutoff = fall.cutoff * time.diff ^ (1/5) / 0.9506785\n \t\n\t# Recalculate stringency parameters for jump detection based on spread of timepoints\n\tjump.cutoffs = jump.multipliers* fall.cutoff \n\n # Recalculate tanking limit based on spread of timepoints\n\ttank.limit = round(tank.limit / time.diff ^ (1/5) * 0.9506785)\n\n\t# Cycle through the indices of input.wells with valid slope estimate\n\tcounter = 0\n\n for(j in 1:length(indices)){\n #######################################################################################\n # Method #1 for finding OD jumps: compare the slope estimate of each point to the #\n # ones for the closest surrounding points. #\n #######################################################################################\n # Get indices of the two closest surrounding timepoints with valid slope estimates. \n\t\tif (j == 1)\n\t\t\tprev.i = indices[2]\n\t\telse\n\t\t\tprev.i = indices[j-1]\n\t\tif (j == length(indices))\n\t\t\tnext.i = indices[length(indices) - 1]\n\t\telse\n\t\t\tnext.i = indices[j+1]\n\t\ti = indices[j]\n\t\t\n\t\t# How the program determines a jump up:\n\t\t# If slope estimate of current timepoint is larger than <jump.cutoffs[2]> times the highest surrounding slope estimate plus <jump.cutoffs[1]> \n\t\t# Add a \"2\" to the indicator variable\n\t\t# How the program determines a fall:\n\t\t# If slope estimate of current timepoint is more negative than <fall.cutoff> \n\t\t# Add a \"5\" to the indicator variable\n\t\t# How the program determines a jump down:\n\t\t# If slope estimate is lower than <fall.cutoff> AND is smaller than <jump.cutoffs[2]> times the lowest surrounding slope estimate minus <jump.cutoffs[1]> \n\t\t# Add a \"6\" to the slope indicator variable\n\t\t#\n\t\t# If none of these are true, add a \"0\" to the indicator variable\n\t\t\n\t\tif (slopes[i] > jump.cutoffs[2] * max(c(slopes[next.i],slopes[prev.i]),0) + jump.cutoffs[1])\n\t\t\tslope.indicator[i] = 2\n\t\telse if (slopes[i] < fall.cutoff){\n\t\t\tslope.indicator[i] = 5\n\t\t\tif (slopes[i] < jump.cutoffs[2] * min(c(slopes[next.i],slopes[prev.i], 0)) - jump.cutoffs[1])\n\t\t\t slope.indicator[i] = 6\t\n\t\t\t}\n\t\telse\n\t\t\tslope.indicator[i] = 0\n\n\n #######################################################################################\n # Method #2 for finding OD jumps: see if each point lies close to a line drawn #\n # between the closest surrounding points. #\n #######################################################################################\n #\n # Use <counter> variable to track the location of each point. If two subsequent points lie farther \n # away than the cutoff from their respectively drawn lines AND are on different sides, then count that as a jump. \n \n\t\tif (j > 1 & j < length(indices)){\n\t\t\n\t\t # Make equation (y=mx+b) for line drawn between two surrounding points\n\t\t\tm = (y[next.i] - y[prev.i])/(x[next.i] - x[prev.i])\n\t\t\tb = y[prev.i] - m * x[prev.i]\t\t\n\t\t\t\n # Estimate y from that line. Points will be judged by how much their true y value deviate from this estimate.\n \n # calculate b for perpendicular line from observed point to drawn line (slope is -1/m)\n b2 = y[i] + x[i]/m\n\n # solve equation for intersection to determine the shortest Euclidean distance between the point and line.\n # assign a sign to the distance based on the vertical distance. \n est.x = (b2 - b) / (m + 1/m)\n est.y = est.x * m + b\n #est.y = m * x[i] + b\n #est.x = x[i]\n \n if(m != 0)\n point.distance = sqrt((y[i]-est.y)^2 + (x[i]-est.x)^2) * sign(y[i]-est.y)\n else # horizontal case\n point.distance = y[i] - b\n \n #print(paste(i, point.distance, slopes[i], jump.cutoffs[2] * max(c(slopes[next.i],slopes[prev.i]),0) + jump.cutoffs[1], point.distance > jump.cutoffs[3])) \n \n\t\t\tcolor = \"gray30\"\n\t\t\t# If the true point exceeds that estimate by more than <jump.cutoffs[3]>, update <counter> to positive.\n\t\t\t# if the counter weas previously negative, mark this as a jump up. \n\t\t\tif (point.distance > jump.cutoffs[3]){\n\t\t\t\tif (counter == -1){\n\t\t\t\t\tslope.indicator[i] = 2\n\t\t\t\t\tcolor = \"red\"\n\t\t\t\t\t}\n\t\t\t\tcounter = 1\n\t\t\t\t}\n\t\t\t# If the true point is under that estimate by more than <jump.cutoffs[3]>, update <counter> to negative.\n\t\t\t# if the counter was previously positive, mark this as a jump down. \n\t\t\telse if (point.distance < -jump.cutoffs[3]){\n\t\t\t\tif (counter == 1){\n\t\t\t\t\tslope.indicator[i] = 6\n\t\t\t\t\tcolor = \"red\"\n\t\t\t\t\t}\n\t\t\t\tcounter = -1\n\t\t\t\t}\n\t\t\t# If the true point lies within <jump.cutoffs[3]> of that estimate, update <counter> to zero. \n\t\t\telse \n\t\t\t\tcounter = 0\n \t\n\t\t\tif(draw)\n # Graphic representation: draw each line used in Method #2 as a dotted line, \n # and highlight in red if a jump was detected\n\t\t\t\tlines(x[c(prev.i, next.i)], y[c(prev.i, next.i)], lty = 2, col = color)\n\t\t\t}\n\t\t}\n\n #######################################################################################\n # Check for tanking by looking for unbroken series of points with falling slopes. #\n #######################################################################################\n #\n # Cycle through <slope.indicator>, adding to <tank> until the end of the curve or until <tank> reaches the <tank.limit>\n\n\ttank = 0\n\ti = 1\n\t\n\twhile(i < length(slope.indicator) & tank < tank.limit){\n\t# If a fall was not detected, reset <tank> to 0. \n\t\tif (is.na(slope.indicator[i]))\n\t\t\ttank = 0 \n # If a fall was detected at a point index greater than <check.start>, add 1 to <tank> .\n \telse if (slope.indicator[i] >= 5 & i > check.start)\n\t\t\ttank = tank + 1\n\t\telse\n\t\t\ttank = 0\n\n\t\ti = i + 1\n\t\t}\n\t\t\n\t# If the above loop was terminated because <tank> reached <tank.limit>, update the \"curve.par\" \n # slot to denote the first point at which tanking started (should be the last index checked minus <tank.limit>)\n # also truncate <slope.indicator> so that it does not include the timepoints after tanking. \n \n\tif (tank == tank.limit){\n\t\tinput.well@curve.par$tanking.start = i - tank.limit\n\t\tslope.indicator = slope.indicator[1:i]\n\t\tif (!silent)\n\t\t cat(\"...tanks at timepoint\", i - tank.limit, \".\\n\")\n\t\t}\n\telse{\n\t\tinput.well@curve.par$tanking.start = NA\n\t\tif (!silent)\n\t\t\tcat(\"...does not tank.\\n\")\n }\n \n #######################################################################################\n # Method #2 of checking for tanking: see if OD falls below cutoff # \n # (as a proportion of max OD) without recovery (according to <tank.limit>) #\n #######################################################################################\n #\n i = check.start\n tanking.start = NA\n while(i < length(y) & is.na(tanking.start)){\n # If the <tank.limit> next ODs beyond i do not reach the cutoff, then mark i as tanking. \n if (all(y[i+(1:tank.limit)] < max(y,na.rm=T)*tank.cutoff, na.rm=T)) \n tanking.start = i\n i = i+1\n }\n \n # Graphic representation: draw the indicators used in Method #1 using the pch symbols in <slope.indicator> \n # slope index = 2: an upward-pointing traingle for an upward jump \n # slope index = 5: a diamond for a fall\n # slope index = 6: a downward-pointing triangle for downward jump \n\tif (draw){\n points(data.from(input.well, remove = F, remove.tanking = F)[which(slope.indicator != 0),], \n \tcol = 2, bg = 2, cex = 1.3, pch = slope.indicator[which(slope.indicator != 0)])\n }\n \n #######################################################################################\n # Decide what to do about any remaining jumps in OD #\n #######################################################################################\n \n\tjump.up = which(slope.indicator == 2)\n\tjump.down = which(slope.indicator == 6)\n\t# <jump.all> is a variable which keeps track of all the jumps, whether up or down. \n\tjump.all = sort(c(match(jump.down, indices), match(jump.up, indices)))\n # commented out; jump not working\n # if (length(jump.all) > 0)\n # add.info = paste(\"Jump(s) detected at timepoint(s)\",paste(indices[jump.all],collapse=\" \"))\n # else\n add.info = \"\"\n \n # If <remove.jumps> is true, use the following automated process to try and remove OD jumps by selectively removing points from analysis.\n # if not, just return the well with the above slot filled. \n \n if (!remove.jumps)\n input.well@add.info = add.info\n else{\n # Cycle through first few jumps (before <check.start>). <remove.initial.jump> is a logical that controls this loop. \n \tremove.initial.jump = T\n \twhile (length(jump.all) > 0 & jump.all[1] < check.start & remove.initial.jump){\n \t\n # If any other jumps are also before <check.start>...\n \t\tif (any(jump.all[-1] < check.start)){ \n \t\t # ...and the next jump is in a different direction, stop the loop and don't remove the first one. \n \t\t\tif(slope.indicator[indices[min(jump.all[-1])]] != slope.indicator[indices[jump.all[1]]])\n \t\t\t\tremove.initial.jump = F\n \t\t\t\t# ...or if the next two jumps are different, stop the loop and don't remove the first one. \n \t\t\telse if(length(jump.all[-1]) > 1 &\n \t\t\t\tslope.indicator[indices[jump.all[2]]] != slope.indicator[indices[jump.all[3]]])\n \t\t\t\tremove.initial.jump = F\t\n \t\t\t# ...otherwise, remove the jump and keep looping. \t\n \t\t\telse \n \t\t\t\tremove.initial.jump = T\n \t\t\t}\n # If no jumps other than the first one are before <check.start>, remove it and keep looping. \n \t\telse \n \t\t\tremove.initial.jump = T\n \t\t\t\n \t # If the initial jump is to be removed, remove all points before the jump from analysis. \n # also delete the initial jump from <jump.all> \t\t\n \t\tif (remove.initial.jump){\n \t\t\tinput.well = remove.points(input.well, 1:(indices[jump.all[1]] - 1))\n \t\t\tinput.well@add.info = paste(add.info, \"and removed.\")\n \t\t\tjump.all = jump.all[-1]\n \t\t\t}\n \t\t}\t\n # If greater than 3 jumps remain, discard the curve as uninterpretable\t\n \tif (length(jump.all) >= 4){\n \t\tinput.well = remove.points(input.well, 1:length(input.well))\n \t\tinput.well@add.info = paste(add.info, \" - data was discarded.\")\n \t\t}\t\n \telse{\n \t# If there are 3 jumps, remove all points after the last one from analysis and delete the last jump from <jump.all>\n \t\tif(length(jump.all) == 3){\n \t\t\tinput.well = remove.points(input.well, indices[jump.all[3]]:length(input.well))\n \t\t\tinput.well@add.info = paste(add.info, \"and removed.\")\n \t\t\tjump.all = jump.all[-3] \n \t\t\t}\n \t\t\t\n\t\t# If there are now 2 jumps...\n \t\tif(length(jump.all) == 2){\n \t\t # ...and they are different (one up, one down), remove the points in between them from analysis.\n \t\t\tif (diff(slope.indicator[indices[jump.all]]) != 0 ){\n \t\t \t\tinput.well = remove.points(input.well, indices[jump.all[1]:(jump.all[2] - 1)])\n \t\t\t\tinput.well@add.info = paste(add.info, \"and removed.\")\n \t\t\t\t}\n \t\t\t\t# ...and they are in the same direction, remove all the points after the first one from analysis.\n \t\t\telse{\n \t\t\t\tinput.well = remove.points(input.well, indices[jump.all[1]]:length(input.well))\n\t \t\t\tinput.well@add.info = paste(add.info, \"and removed.\")\n \t\t\t\t}\t\t\t\t\n \t\t\t}\n\t\t# If there is only one jump, remove all points after it from analysis. \n \t\telse if (length(jump.all == 1)){\n \t\t\tinput.well = remove.points(input.well, indices[jump.all[1]]:length(input.well))\n \t\t\tinput.well@add.info = paste(add.info, \"and removed.\")\n \t\t\tjump.all = jump.all[-1] \n \t\t\t}\n \t\t}\n \t}\n \tif(!silent)\n \t cat(\"\\t\", input.well@add.info)\n\treturn(input.well)\n\t}\n\t\n########################################################################\n# #\n# Check wells for growth, remove from analysis if OD is too low # \n# #\n########################################################################\n#\n# The well will be tagged with no.growth = T in the slot \"curve.par\" if raw OD values (except for <points.to.remove>)\n# do not increase beyond <growth.cutoff> above the specified time of inoculation for that well (<start.index>) \n\ncheck.growth = function(input.well, growth.cutoff, start.index = 2){\n\n # Get raw ODs (not including <points.to.remove>) and slope estimates from the well \n # as well as OD at inoculation timepoint <start.index>\n\traw.ODs = raw.data(input.well)[,2]\n start.OD = raw.ODs[start.index]\n \n raw.ODs[input.well@screen.data$Remove] = NA\n\tslope.estimates = slopes(input.well, remove.tanking = T, na.rm = T)\n\n # If fewer than 3 points remain in the analysis with valid slope estimates, discard the well. \n\tif (length(slope.estimates) < 3 | all(is.na(slope.estimates)))\n\t\tinput.well@curve.par$no.growth= T\t\t\n\telse{\n\t# If there are no points at all in the raw ODs \n\t\tif(all(is.na(raw.ODs)))\n\t\t\tinput.well@curve.par$no.growth = T\n\t\telse if(max(raw.ODs, na.rm=T) - start.OD < growth.cutoff) # See if OD increases by at least <growth.cutoff> above\n\t\t\tinput.well@curve.par$no.growth = T\n\t\telse\n\t\t\tinput.well@curve.par$no.growth = F\n\t\t}\n if(all(raw.data(input.well)[,2] - raw.data(input.well)[start.index,2] < growth.cutoff)) \n input.well@add.info = \"\" # This is simply to reduce the amount of unnecessary info in the output. \n # If the well is below growth cutoff anyway, don't bother reporting other errors. \n\treturn(input.well)\n\t}\n\n\n",
|
|
"created" : 1425413252161.000,
|
|
"dirty" : false,
|
|
"encoding" : "UTF-8",
|
|
"folds" : "",
|
|
"hash" : "2162891425",
|
|
"id" : "1B6124D6",
|
|
"lastKnownWriteTime" : 1424208623,
|
|
"path" : "~/Documents/GCAT4/trunk/R/GCAT/R/slope.analysis.R",
|
|
"project_path" : "R/slope.analysis.R",
|
|
"properties" : {
|
|
},
|
|
"source_on_save" : false,
|
|
"type" : "r_source"
|
|
} |