How to make an R function return multiple columns and append them to a data frame?
문제
Starting with this data frame
myDF = structure(list(Value = c(-2, -1, 0, 1, 2)), .Names = "Value", row.names = c(NA, 5L), class = "data.frame")
Suppose I want to run this function on every row of myDF$Value
getNumberInfo <- function(x) {
if(x %% 2 ==0) evenness = "Even" else evenness="Odd"
if(x > 0) positivity = "Positive" else positivity = "NonPositive"
if (positivity == "Positive") logX = log(x) else logX=NA
c(evenness,positivity,logX)
}
... to get this data frame
structure(list(Value = c(-2, -1, 0, 1, 2), Evenness = c("Even",
"Odd", "Even", "Odd", "Even"), Positivity = c("NonPositive",
"NonPositive", "NonPositive", "Positive", "Positive"), Log = c(NA,
NA, NA, "0", "0.693147180559945")), row.names = c(NA, 5L), .Names = c("Value",
"Evenness", "Positivity", "Log"), class = "data.frame")
해결책
You might want to change your getNumberInfo
function to return a list rather than a vector, so that the values can have different types. As it is, they're all being cast to strings, which probably isn't what you want for logX
.
getNumberInfo <- function(x) {
if(x %% 2 ==0) evenness = "Even" else evenness="Odd"
if(x > 0) positivity = "Positive" else positivity = "NonPositive"
if (positivity == "Positive") logX = log(x) else logX=NA
list(evenness,positivity,logX)
}
Furthermore, you can use the names to a somewhat better effect so that you don't have to repeat them:
getNumberInfo <- function(x) {
list(evenness = if(x %% 2 ==0) "Even" else "Odd",
positivity = if(x > 0) "Positive" else "NonPositive",
logX = if(x > 0) log(x) else NA)
}
Then the solution becomes simple:
> cbind(myDF, t(sapply(myDF$Value, getNumberInfo)))
Value evenness positivity logX
1 -2 Even NonPositive NA
2 -1 Odd NonPositive NA
3 0 Even NonPositive NA
4 1 Odd Positive 0
5 2 Even Positive 0.6931472
Finally, if you use ifelse
(which can work on vectors) instead of if
, it gets even simpler because you don't have to call apply
:
getNumberInfo <- function(x) {
list(evenness = ifelse(x %% 2 ==0, "Even", "Odd"),
positivity = ifelse(x > 0, "Positive", "NonPositive"),
logX = ifelse(x > 0, log(x), NA))
}
> cbind(myDF, getNumberInfo(myDF$Value))
Value evenness positivity logX
1 -2 Even NonPositive NA
2 -1 Odd NonPositive NA
3 0 Even NonPositive NA
4 1 Odd Positive 0.0000000
5 2 Even Positive 0.6931472
That last solution emits a warning, because it's actually computing the log of every element, not just those with x>0
. Not sure the most elegant way to deal with that.
다른 팁
How about:
out <- cbind(myDF, t(apply(myDF, 1, getNumberInfo)))
colnames(out) <- c('Value', 'Evenness', 'Positivity', 'Log')
Which gives you:
Value Evenness Positivity Log 1 -2 Even NonPositive NA 2 -1 Odd NonPositive NA 3 0 Even NonPositive NA 4 1 Odd Positive 0 5 2 Even Positive 0.693147180559945
Another alternative:
> library(plyr)
> df <- mdply(myDF, getNumberInfo)
> names(df) <- c('Value', 'Evenness', 'Positivity', 'Log')
> df
Value Evenness Positivity Log
1 -2 Even NonPositive NA
2 -1 Odd NonPositive NA
3 0 Even NonPositive NA
4 1 Odd Positive 0.0000000
5 2 Even Positive 0.6931472