【问题标题】:How can I compare and sort two values within a data frame in R?如何在 R 的数据框中比较和排序两个值?
【发布时间】:2016-10-27 09:36:37
【问题描述】:

这是我的第一篇文章,所以请放轻松;D

对于我参与的一些研究,我们为脊髓切片生成了两个面积测量值。较小的尺寸是指受伤形成的空腔,较大的区域是整个脊髓。这些测量是在 Photoshop 中进行的,并以相同的文档名称导出,但值明显不同。 例如,

$`T7-B9_TileScan_005_Merging001_ch00.tif`
              Label                               Document         Area
1827 Measurement 39 T7-B9_TileScan_005_Merging001_ch00.tif    92,041.52
1831 Measurement 40 T7-B9_TileScan_005_Merging001_ch00.tif 3,952,865.00

这实际上是我使用 R 的子集函数删除数据创建的简化版本。我必须这样做的原因是疤痕区域的范围与总脐带区域的范围重叠,这意味着它们不能通过简单的尺寸排除来过滤。

我的示例数据集可以在here 找到。 要生成这个,请在​​此处关注我的 [EDITED] 工作。

Scar.Ablation.Data <- read.csv("/Scar Ablation Data.csv", stringsAsFactors=F)

添加 stringsAsFactors=F 更正了稍后生成的错误。

test1 <- subset(Scar.Ablation.Data, Count != "", select = c(Label,Document,Area))

删除所有没有 Count 值的数据。当 Photoshop 导出数据时,它使用了冗余测量值。然而,所有这些冗余测量都不包含 Count 值,因此可以将它们删除。建议的替代方法不起作用,因为 R 没有读取 Count 列中的任何值作为 NA。

fileList = split(test1,test1$Document)

生成一个列表,其中测量值由文档名称分隔。

spineAreas = lapply(fileList, function(x) x[which(x$Area==max(x$Area)), ])

获取每个列表(表示给定文件名的所有数据),然后查找并返回每个文件面积最大的行中的数据。

scarAreas = lapply(fileList, function(x) x[which(x$Area==min(x$Area)), ])

我们想要每个文件的所有行的数据,其面积小于最大面积。 Lapply 返回一个列表,所以现在我们要将它们转回 dataFrames

spineData = do.call(rbind,spineAreas)
scarData = do.call(rbind,scarAreas)
row.names(spineData)=NULL #clean up row names
row.names(scarData)=NULL
write.csv(scarData, "/scarData.csv")
write.csv(spineData, "/spineData.csv")

比较我的导出时,出现以下问题:

  1. spineData 包含 Null 值,但 ScarData 没有。

通过在scarArea 的函数中将x$Area&lt;max 切换为x$Area==min 解决了这个问题。输出虽然仍然不正确,但并没有因此修改而改变。

  1. 区域之间的比较并不总是有效。例如,对于样本“C1-B3_TileScan_002_Merging001_ch00.tif”,疤痕报告的面积高于脐带。

我尝试使用aggregate() 函数尝试不同的比较方法,但这返回的数据与使用上述方法生成的数据完全相同。然而,R 正在计算这些比较,它认为它正在做出正确的决定。这可能表明我的区域数值存在某种格式或导入问题。

spineAreas2 = aggregate(Area ~ Document, data = test1, max)
scarAreas2 = aggregate(Area ~ Document, data = test1, min)

spineData2 = do.call(rbind,spineAreas2)
scarData2 = do.call(rbind,scarAreas2)

row.names(spineData2)=NULL #clean up row names
row.names(scarData2)=NULL #clean up row names

do.call(rbind, lapply(spineAreas, data.frame, stringsAsFactors=FALSE))
do.call(rbind, lapply(scarAreas, data.frame, stringsAsFactors=FALSE))
#Then clean up row names as in first example, or pass row.names=F 
#when writing to a .csv file

write.csv(scarData2, "C/scarData2.csv")
write.csv(spineData2, "CspineData2.csv")

我可以将 Null 替换为 0 或 NA,我可能会尝试这样做以解决此问题。感谢@Cole 继续帮助解决这个问题,非常感谢。

【问题讨论】:

    标签: r sorting compare subset


    【解决方案1】:

    问题总结

    现在我有了一个样本数据集,我可以看到一些问题。

    第一个问题是您确实没有拥有.csv 文件。 csv 代表逗号分隔值,如您所见,您的文件不包含值之间的逗号。看起来它是一个 tsv制表符分隔值 文件。在 R 中,您希望使用 read.delim() 函数读取此内容,如下所示:

    ablationData = read.delim("Scar Ablation Data.txt",stringsAsFactors=F)
    

    (如果确实是制表符分隔,您可能还需要考虑使用 .tsv 扩展名来命名您的数据)

    读入数据后很明显

    1. 对于“错误”读取,文件包含“Null”,这与 R 中的 NULL 对象不同(注意全部大写)。使用 x=="Null" 是测试这些的正确方法(就像你以前做的那样)。
    2. 没有Count 数据的读取由"" 值表示。我猜这与.tsv 文件中不存在任何值的性质有关,这些值表示为"",因为选项卡之间没有任何内容。请注意,如果您在哪里使用不同的文件格式,例如 .csv "" 将被读入 NA 这归结为 R read.xxx 函数的处理方式不同的文件类型,这是一件值得记住的好事情。
    3. Count 列表示每次测量的“特征”数量。似乎每个measurement 都有一个measurement # 行,它是该测量的汇总摘要。然后measurement 的每个feature 都有自己的行,由measurement #-Feature # 表示。根据您对问题的描述,您希望删除单个“特征”测量值并仅比较每个测量值集聚合值。我不确定这是否是您真正想要/想要做的,所以我会仔细考虑为什么要删除单个 feature 行,因为它们肯定不是重复/冗余值正如你在上面所说的那样。
    4. 如上所述,我们的许多列中有"""Null" 值,否则这些值包含数字输入。这将导致这些列中的所有值转换为character 类型而不是numeric。这就是为什么之前的排序不起作用的原因,因为max()charactersnumerics 上的工作方式非常不同。删除有问题的 "" and "Null" 值后,我们必须将所需的列转换为 numeric 数据类型。
    5. 数据的另一个问题是它的数字中同时包含,.。 R 不喜欢 , 的数字,也不知道如何解释它们。因此,我们需要删除它们

    总结:

    • 读入数据(.tsv
    • 分离出所有 "Null" 值(请参阅下面的注释)
    • 删除所有单独的 feature 测量值,仅保留每个 measurement 集的聚合数据。
    • 从号码中删除所有,
    • 将仅包含数字的列转换为numeric
    • 按文件名分隔数据
    • 处理每个文件
      • 找到聚合measurement 和最大的Area。这代表脊柱
      • 所有其他measurement 值表示伤疤。
      • 将结果分成两个不同的数据集。一种用于疤痕,一种用于脊柱。
    • 重新添加“Null”值(参见下面的注释)

    一个问题:您确定要根据文件进行分离然后只比较聚合测量值,还是真的要根据测量值进行分离然后比较该度量中的每个特征

    上一个答案的注释

    spineData 应该是唯一包含 "Null" 值的列表。这是因为完全由"Null" 组成的数据集的max()min() 就是"Null"。因此== max(data) 对每个"Null" 数据点(即"Null"=="Null")都为真,但&lt; max(data) 对每个"Null" 数据点(即"Null"&lt; "Null")都为假。我真的不认为你想使用==min(data),因为这样你就会为每个有非“Null”数据的文件丢弃所有中间值(可能是有效的疤痕测量值)。

    如果您真的想将 "Null" 读取保留在您的数据集中,我建议您将它们取出,处理其余数据,然后在最后添加它们。

    解决方案

    读入数据。

     data = read.delim("Scar Ablation Data.tsv",stringsAsFactors=F)
    

    分离出"Null" 测量值

    data2 = data[-which(data$Area=="Null"),]
    

    删除所有feature 测量值,只保留每个measurement 的聚合数据。仅保留 LabelDocumentArea 列。

    data2 = data2[-which(data2$Count==""),c("Label","Document","Area")]
    

    对于包含数字数据的所需列,从数字中删除 , 并强制转换为类型 numeric

    data2$Area = as.numeric(gsub(",","",data2$Area))
    

    按文件/Document 名称分隔数据。

    fileList = split(data2,data2$Document)
    

    处理每个文件。最大的Area 值代表脊柱,所有其他(较小的)值代表疤痕。这些语句中的每一个都返回一个包含我们想要的结果的列表。

    spineAreas = lapply(fileList, function(x) x[which(x$Area==max(x$Area)), ])
    scarAreas = lapply(fileList, function(x) x[which(x$Area<max(x$Area)), ])
    

    转换回dataFrame。这里我添加了一个额外的步骤来避免我们的数据被转换为因子。

    spineAreas = do.call(rbind, lapply(spineAreas, data.frame, stringsAsFactors=FALSE))
    scarAreas = do.call(rbind, lapply(scarAreas, data.frame, stringsAsFactors=FALSE))
    

    添加带有"Null" 的文件会读回并清理行名。 只有在完全完成数据分析后才这样做

    nullDocs = match(unique(data$Document[data$Label=="Null"]),data$Document)
    nullDocs = data.frame(data[nullDocs,c("Label","Document","Area")],stringsAsFactors=F)
    scarAreas = rbind(nullDocs,scarAreas)
    spineAreas = rbind(nullDocs,spineAreas)
    row.names(scarAreas)=NULL
    row.names(spineAreas)=NULL
    

    请注意

    通过重新添加"Null" 值,我们的Area 列将被强制恢复为character 类型,因为列中的每个元素都必须具有相同的数据类型。这很重要,因为这意味着您无法在 R 中对您的数据进行任何有意义的操作。

    例如:spineAreas$Area&gt;scarAreas$Area 将返回

    [1] FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE  TRUE
    [23]  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE  TRUE
    

    这可能会让我们相信我们没有正确排序数据。

    但是:as.numeric(spineAreas$Area)&gt;as.numeric(scarAreas$Area) 将返回

    [1]   NA   NA   NA TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
    [28] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
    

    这表明前 3 个值的字符串(在本例中为 "Null")被替换为 NA,然后表明我们的数据已正确排序。

    因此,当您完全完成数据分析后,要么添加 "Null" 值,要么将所需的列重新转换为数字(例如 spineAreas$Area = as.numeric(spineAreas$Area)

    如果你想避免这种混乱的打字业务一起(首选)

    读入您的数据,以便所有"""Null" 都由NA 表示。这将使生活变得更轻松,但不会让您不必删除 , 并将您的数据转换为数字。

    以下是您需要更改的行

    data = read.delim("Scar Ablation Data.tsv",na.strings=c("NA","Null",""),stringsAsFactors=F)
    data2 = data[-which(is.na(data$Area)),]
    data2 = data2[-which(is.na(data2$Count)),c("Label","Document","Area")]
    nullDocs = match(unique(data$Document[is.na(data$Label)]),data$Document)
    

    这将使您的数据保持为numeric,即使在添加回空读取后也是如此,这可能是做事的首选方式。

    【讨论】:

    • 非常感谢您在这个问题上的帮助!我确实学到了很多关于 R 中数据格式的敏感度和挑战性的知识。您的回答完美无缺!关于您的解决方案的几个问题: 1. 当您讨论 Count 值以及功能是什么时,也许我没有使用最好的词来描述它们。特征是整个测量的组成部分。这些“特征”是 Photoshop 在捕获区域数据时创建的,实际上并不代表我们希望调查的任何内容。除此之外,我认为一切都已得到解答!
    • 没问题,很高兴为您提供帮助!我会说你的数据肯定比平时复杂一点,因为它是由另一个具有与 R 不同约定的程序生成的。动态类型会使事情变得有点棘手,但通常不会那么令人头疼。
    【解决方案2】:

    好的,所以如果我理解正确,您想要 a) 清理数据(您已经完成)然后 b) 按文件名划分数据(也已经完成)然后最后 c) 比较每个区域内的面积测量值文件类型,较小的是疤痕,最大的是脊柱。您想将每一项分类到一个单独的列表中,一个用于疤痕数据,另一个用于脊柱数据(问题)。

    为此,我们将使用 lapply 函数。它获取矩阵、数组或数据框的每个元素,并对其应用一个函数。这里我们编写自己的函数。它获取每个列表(表示给定文件名的所有数据),然后查找并返回每个文件面积最大的行中的数据。

    spineAreas = lapply(fileList, function(x) x[which(x$Area==max(x$Area)), ])
    

    接下来我们做同样的事情,但这次我们想要更小的疤痕区域。因此,我们想要每个文件的所有行的数据,其面积小于最大面积。 这种方法假设每个文件的最大区域是脊髓横断面,所有其他区域代表疤痕。

    scarAreas = lapply(fileList, function(x) x[which(x$Area<max(x$Area)), ])
    

    Lapply 返回一个列表,所以现在我们要将它们转回dataFrames

    spineData = do.call(rbind,spineAreas)
    scarData = do.call(rbind,scarAreas)
    #clean up row names
    row.names(spineData)=NULL
    row.names(scarData)=NULL
    

    上述方法会将每个字符串转换为dataFrame 中的一个因素。如果您不希望它们成为因素(有时会导致问题,因为它们不能很好地与某些功能配合使用),那么您可以执行以下操作。

    do.call(rbind, lapply(spineAreas, data.frame, stringsAsFactors=FALSE))
    do.call(rbind, lapply(scarAreas, data.frame, stringsAsFactors=FALSE))
    #Then clean up row names as in first example, or pass row.names=F 
    #when writing to a .csv file
    

    如果这就是你想要完成的目标,请告诉我。

    【讨论】:

    • 感谢您的回复!我已经按照您的建议更新了我的原始帖子,并整理了一些代码。然而,这段代码将在我们的数据收集结束时运行一次,所以我认为子集函数仍然可以很好地工作。关于你的问题 4,我已经更新了我的最后一段,希望能稍微澄清一下我的意思。简而言之,我想比较并找到较小的区域(因为腔总是小于总的线面积)并将所有的腔测量分开到他们自己的文档中。
    • @AustinHovland 更新了实际答案
    • 感谢您的帮助!我尝试按照列出的方式运行您的代码,但遇到了错误“Summary.factor(integer(0), na.rm = FALSE) 中的错误:'max' 对因子没有意义”意味着我需要转换我的“因素”转化为“数字”。为此,我尝试为我的 fileList 运行“as.numeric.factor
    • @AustinHovland 尝试使用 read.csv("Scarablation data.csv", stringsAsFactors=F) 读取您的数据。我今天大部分时间都在远离我的主计算机,但我会尽快与您联系。
    • 您的Areas 应该是数字。对我来说,这暗示了您的数据文件的设置方式有些“有趣”。有没有可能是用引号导出的? IE。 "123","321" 而不是 123,321 如果是这种情况,我相信 read.csv 可以选择剥离 "s
    猜你喜欢
    • 1970-01-01
    • 2016-12-03
    • 2022-01-10
    • 2018-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多