从元素列表到化学式

Going from a list of elements to chemical formula

我有一个元素组成列表,每个元素都在它自己的行中。有时这些元素有一个零。

   C H N O S
1  5 5 0 0 0
2  6 4 1 0 1
3  4 6 2 1 0

我需要将它们组合起来以便它们阅读,例如C5H5、C6H4NS、C4H6N2O。 这意味着对于任何值为“1”的元素,我应该只取列名,对于任何值为 0 的元素,应该完全跳过该列。

我不太确定从哪里开始。我可以添加一个新列以使其更容易跨列阅读,例如

   c C h H n N o O s S
1  C 5 H 5 N 0 O 0 S 0
2  C 6 H 4 N 1 O 0 S 1
3  C 4 H 6 N 2 O 1 S 0

这样,我只需要输出一个字符串,但我需要忽略任何零值,并在元素名称后面删除一个。

这是一个基本的 R 解决方案:

df = read.table(text = "
C H N O S
5 5 0 0 0
6 4 1 0 1
4 6 2 1 0
", header=T)

apply(df, 1, function(x){return(gsub('1', '', paste0(colnames(df)[x > 0], x[x > 0], collapse='')))})
[1] "C5H5"    "C6H4NS"  "C4H6N2O"

paste0(colnames(df)[x > 0], x[x > 0], collapse='') 将行值大于零的列名称粘贴在一起。 gsub 然后删除那些。 apply 对数据框中的每一行执行此操作。

这是一个使用了一些整形的tidyverse解决方案:

df = read.table(text = "
C H N O S
5 5 0 0 0
6 4 1 0 1
4 6 2 1 0
", header=T)

library(tidyverse)

df %>%
  mutate(id = row_number()) %>%                      # add row id
  gather(key, value, -id) %>%                        # reshape data
  filter(value != 0) %>%                             # remove any zero rows
  mutate(value = ifelse(value == 1, "", value)) %>%  # replace 1 with ""
  group_by(id) %>%                                   # for each row
  summarise(v = paste0(key, value, collapse = ""))   # create the string value

# # A tibble: 3 x 2
#      id v      
#   <int> <chr>  
# 1     1 C5H5   
# 2     2 C6H4NS 
# 3     3 C4H6N2O

假设输入矩阵 m 与末尾注释中给出的一样——如果它是使用 as.matrix 的数据框,则将其转换为矩阵。

现在创建一个与 m 形状相同且仅包含字母的矩阵,因此现在 lets 包含字母而 m 包含数字。然后将字母和数字粘贴在一起,并将数字为零的单元格替换为空字符串。还要用字母替换数字为 1 的所有单元格。最后将每一行粘贴在一起。没有使用包,也没有使用循环或 *apply。

lets <-  t(replace(t(m), TRUE, colnames(m)))
mm <- paste0(lets, m)
mm <- replace(mm, m == 0, "")
mm <- ifelse(m == 1, lets, mm)
do.call("paste0", as.data.frame(mm))
## [1] "C5H5"    "C6H4NS"  "C4H6N2O"

备注

假设可重现形式的输入矩阵m为:

m <- matrix(c(5, 6, 4, 5, 4, 6, 0, 1, 2, 0, 0, 1, 0, 1, 0), 3, 5,
  dimnames = list(NULL, c("C", "H", "N", "O", "S")))

另一个避免 apply 边距 1 的想法,

gsub('1', '', sapply(split(df, 1:nrow(df)), function(i) 
                                 paste(paste0(names(i)[i != 0], i[i != 0]), collapse = '')))

#        1         2         3 
#   "C5H5"  "C6H4NS" "C4H6N2O"

另一种选择

library(dplyr)
#Get indices of all non-zero numbers in the dataframe
inds <- which(df!=0, arr.ind = TRUE)

#Create a dataframe with row index, column index and value at that position
vals <- data.frame(inds, val = df[inds])

#For each row paste the name of the column and value together and then replace 1
vals %>%
  group_by(row) %>%
  summarise(chemical = paste0(names(df)[col], val,collapse = "")) %>%
  mutate(chemical = gsub("[1]", "", chemical))

#   row chemical
#  <int> <chr>   
#1     1 C5H5    
#2     2 C6H4NS  
#3     3 C4H6N2O