R - Erstellen Sie mehrere neue Spalten mit bedingten Anweisungen

Ich frage mich, ob es eine Möglichkeit gibt, mehrere Spalten basierend auf einer Bedingung (en) zu erstellen.

Zum Beispiel habe ich unten einen Datenrahmen mit Daten und möchte zwei Spalten basierend auf der Basis von ccy erstellen. Eine Spalte ist eine gbp-Konvertierungsrate des ccy und die andere ist eine cad-Konvertierungsrate.

Wenn ich die Mutaten weitergebe, kann ich sie zum Laufen bringen, aber es gibt Wiederholungen (und in meinem eigentlichen Problem habe ich eine komplexe Liste von ifelse, so dass das Wiederholen des Codes für jede Spalte viel Wiederholung verursachen würde).

df <- structure(list(product = c('option', 'forward', 'forward', 'option'),
                 ccy = c('usd', 'usd', 'eur', 'usd'),
                 amount = c(1000, 2000, 1000, 5000)),
            .Names = c('product', 'ccy', 'amount'),
            row.names = c(NA, 4L),
            class = "data.frame")
df
  product ccy amount
1  option usd   1000
2 forward usd   2000
3 forward eur   1000
4  option usd   5000

df %>% mutate(gbp_amount = 
                  ifelse(ccy == 'usd', round(amount / 1.8, 2),
                         ifelse(ccy == 'eur', round(amount / 1.3, 2),
                                'not_converted'))) %>% 
    mutate(cad_amount = 
               ifelse(ccy == 'usd', round(amount / 0.85, 2),
                      ifelse(ccy == 'eur', round(amount / .7, 2),
                             'not_converted')))

  product ccy amount gbp_amount cad_amount
1  option usd   1000     555.56    1176.47
2 forward usd   2000    1111.11    2352.94
3 forward eur   1000     769.23    1428.57
4  option usd   5000    2777.78    5882.35

Gibt es eine Möglichkeit, mehrere Spalten basierend auf einer einzelnen if-Bedingung zu erstellen?

Zum Beispiel so etwas wie dieser Pseudocode ...

df %>% ifelse(df$ccy == 'usd',
        (mutate(gbp_amount = round(amount / 1.8, 2)),
        mutate(cad_amount = round(amount / 0.85, 2))),
    ifelse(df$ccy == 'eur',
        (mutate(gbp_amount = round(amount / 1.3, 2)),
        mutate(cad_amount = round(amount / 0.7, 2))),
        'not_converted'))
r
1
antimuon 29 Juni 2018 im 21:45

3 Antworten

Beste Antwort

Sie können SQL-ähnliche Verknüpfungen verwenden, wenn Sie viele "gleiche" Bedingungen haben.

Ich verwende die data.table -Syntax, aber Sie können dies auch dplyr tun:

library(data.table)

setDT(df)

# add a row which cannot be found ("joined") to demonstrate missing rates
df <- rbind(df, data.table(product = "option", ccy = "aud", amount = 3000))
df

lookup <- data.table(ccy      = c("usd", "eur"),
                     gbp_rate = c( 1.8,   1.3),
                     cad_rate = c( 0.85,  0.7))
lookup
#    ccy gbp_rate cad_rate
# 1: usd      1.8     0.85
# 2: eur      1.3     0.70

df[lookup, `:=`(gbp_amount = round(amount / gbp_rate, 2),
                cad_amount = round(amount / cad_rate, 2)),
                on = "ccy"]
df
#    product ccy amount gbp_amount cad_amount
# 1:  option usd   1000     555.56    1176.47
# 2: forward usd   2000    1111.11    2352.94
# 3: forward eur   1000     769.23    1428.57
# 4:  option usd   5000    2777.78    5882.35
# 5:  option aud   3000         NA         NA

Sie müssen das Ergebnis nach Belieben sortieren und die Suchfehler (fehlende Conversion-Raten) mit einem anderen Wert als NA markieren, wenn Sie möchten (jedoch nicht mit der Zeichenfolge "not_converted" wie in Ihrer Frage, da sich diese mischen würde den Datentyp der Spalte erhöhen (doppelt gegen Zeichen).

2
R Yoda 30 Juni 2018 im 07:26

Erwägen Sie, einen Raten -Datensatz zu erstellen und mit Ihrem Original zusammenzuführen, um verschachtelte ifelse zu vermeiden:

rates_df <- data.frame(ccy = c('usd', 'eur'),
                       type = c('gbp', 'gbp', 'cad', 'cad'),
                       rate = c(1.8, 1.3, 0.85, 0.7),
                       stringsAsFactors = FALSE)    
rates_df

df %>% 
  inner_join(rates_df, by="ccy") %>%
  mutate(gbp_amount = ifelse(type=="gbp", round(amount / rate, 2), 0),
         cad_amount = ifelse(type=="cad", round(amount / rate, 2), 0)) %>%
  select(product, ccy, matches("amount")) %>%
  group_by(product, ccy, amount) %>%
  summarise_all(sum)

# # A tibble: 4 x 5
# # Groups:   product, ccy [?]
#   product   ccy amount gbp_amount cad_amount
#     <chr> <chr>  <dbl>      <dbl>      <dbl>
# 1 forward   eur   1000     769.23    1428.57
# 2 forward   usd   2000    1111.11    2352.94
# 3  option   usd   1000     555.56    1176.47
# 4  option   usd   5000    2777.78    5882.35
3
Parfait 29 Juni 2018 im 22:05

Sie müssen ein for-loop verwenden, wenn Sie mehrere Aktionen ausführen möchten. Die Lösung von @R Yoda ist wahrscheinlich besser. Wie er sagte, würde ich NA anstelle einer Zeichenfolge verwenden, damit Sie keine Datentypen in einem Vektor mischen, da sonst standardmäßig Zeichen verwendet werden.

for (i in 1:nrow(df)) {
  if(df$ccy[i] == "usd") {
    df$gbp_amount[i] <- round(df$amount[i] / 1.8, 2);
    df$cad_amount[i] <- round(df$amount[i] / 0.85, 2);
  } else {
    NA
 }
  if(df$ccy[i] == "eur") {
    df$gbp_amount[i] <- round(df$amount[i] / 1.3, 2);
    df$cad_amount[i] <- round(df$amount[i] / 0.7, 2);
  } else {
    NA
  }
}
0
Anonymous coward 29 Juni 2018 im 21:12