Apply auf alle Gruppen eines Dataframes anwenden

Allgemeine Statistik mit R, die Test-Methode ist noch nicht bekannt, ich habe noch keinen Plan!

Moderatoren: EDi, jogo

AlexWue
Beiträge: 9
Registriert: Mo Okt 29, 2018 3:46 pm

Apply auf alle Gruppen eines Dataframes anwenden

Beitrag von AlexWue »

Hallo Alex,

bitte verwende niemals "<<-". Das ist schlimmer als einfach nur schlechter Stil:

Code: Alles auswählen

library("fortunes")
fortune(174)
Schreibe statt dessen:

Code: Alles auswählen

zuteilung <- function(df){
    for (i in 1:nrow(df)){
      ifelse((df$volumen_teil[i] <= volumen_kiste), (df$id_kiste[i] <- id_kiste) & (volumen_kiste <- volumen_kiste - df$volumen_teil[i]), (id_kiste <- id_kiste +1) & (df$id_kiste[i] <- id_kiste) & (volumen_kiste <- (100 - volumen_teil[i]))) 
    }
    df
}
df <- zuteilung(df)
Gruß, Jörg
jogo
Beiträge: 2085
Registriert: Fr Okt 07, 2016 8:25 am

Re: Apply auf alle Gruppen eines Dataframes anwenden

Beitrag von jogo »

Hallo Alex,

oje, jetzt hatte ich auf editieren gedrückt statt antworten.
Das tut mir fürchterlich leid.

Kannst Du die originale Frage nochmal posten?

Ich bitte um Entschuldigung
Jörg
p.s.: Inzwischen bin ich mir nicht sicher, ob Du nicht etwa

Code: Alles auswählen

zuteilung <- function(df){
  for (i in 1:nrow(df)) {
    if (df$volumen_teil[i] <= volumen_kiste) {
      df$id_kiste[i] <- id_kiste
      volumen_kiste  <- volumen_kiste - df$volumen_teil[i] 
    } else {
      id_kiste <- id_kiste +1
      df$id_kiste[i] <- id_kiste
      volumen_kiste <- (100 - volumen_teil[i])
    }
  }
  df
}
df <- zuteilung(df)
möchtest. Aber auch hier besteht das Problem, dass nach Ende der Funktion volumen_kiste unverändert ist.
Noch etwas ist unschön: Du verwendest das Volumen der neuen Kiste als numerische Konstante (man nennt es hard coded) - das macht ein Programm weniger pflegeleicht :!:
bigben
Beiträge: 2771
Registriert: Mi Okt 12, 2016 9:09 am

Re: Apply auf alle Gruppen eines Dataframes anwenden

Beitrag von bigben »

Und wenn wir schon dabei sind, dann schreibe möglichst auch niemals `df <-`.

df ist in R der Name einer Funktion. Damit kann man die F-Verteilung abfragen. Wenn man Werte in Variablennamen speichert, die zugleich Funktionsnamen sind, funktioniert erstmal alles, wie gewohnt. Wenn man aber einen Fehler macht, werden die Fehlermeldungen rasch unbrauchbar.

Das von mir angesprochene Problem ist nicht so gefährlich wie der Gebrauch von `<<-`, aber man sollte sich von Anfang an angewöhnen, keinen data.frame `df` zu nennen. `d.f` ist dagegen völlig in Ordnung.

LG,
Bernhard
---
Programmiere stets so, dass die Maxime Deines Programmierstils Grundlage allgemeiner Gesetzgebung sein könnte
AlexWue
Beiträge: 9
Registriert: Mo Okt 29, 2018 3:46 pm

Re: Apply auf alle Gruppen eines Dataframes anwenden

Beitrag von AlexWue »

Hallo Jörg,

danke schonmal für den Hinweis mit <<-.


Hier nochmal die ursprüngliche Frage:

Ich möchte verschieden große Teile in Kisten packen und dabei für jedes Teil angeben, in welche Kiste es gepackt werden soll.
Für jedes Teil muss also überprüft werden, ob es noch in die aktuelle Kiste passt, oder eine neue benötigt wird (wird eine neue Kiste benötigt, ist die alte quasi raus und darf nicht mehr befüllt werden).
Zusätzlich sind alle Teile in Gruppen untergliedert, wobei in einer Kiste nur Teile der gleichen Gruppe zusammengepackt werden dürfen.

Aktuell benutze ich eine for-Schleife, hier der verwendete Code:

Code: Alles auswählen

  volumen_teil <- c(20, 50, 90, 20, 30, 10, 10, 80, 10, 20, 50)
  id_teil <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
  gruppe <- c(1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2)
  df <- data.frame(id_teil, gruppe, volumen_teil)
  
  volumen_kiste = 100
  id_kiste = 1

  zuteilung <- function(df){
    for (i in 1:nrow(df)){
      ifelse((df$volumen_teil[i] <= volumen_kiste), (df$id_kiste[i] <- id_kiste) & (volumen_kiste <- volumen_kiste - df$volumen_teil[i]), (id_kiste <- id_kiste +1) & (df$id_kiste[i] <- id_kiste) & (volumen_kiste <- (100 - volumen_teil[i]))) 
    }
    df
  }
  df <- zuteilung(df)
Zusätzlich ist noch Vektorisierung und die Überprüfung auf die gleiche Gruppe eingebaut. Zur besseren Verständlichkeit habe ich das hier allerdings weggelassen.

Das Problem ist, dass die for-Schleife zu langsam ist.

Die Gruppen sind unabhängig voneinander und könnten daher parallel bearbeitet werden.
Ich möchte gerne die for-Schleife nicht für den gesamten dataframe, sondern nur für eine Gruppe durchlaufen lassen.
Diese Funktion soll dann über eines der "apply" packages auf alle Gruppen angewandt werden.

Was ich nicht hinbekomme, ist der Funktion zu sagen, dass ich nur auf die Zeilen der entsprechenden Gruppe (anstatt auf alle Zeilen des gesamten dataframes) zugreifen möchte und diese Funktion dann auf alle Gruppen zu "mappen".


Über jegliche Hilfe wäre ich bereits sehr dankbar!

Viele Grüße
Alex
AlexWue
Beiträge: 9
Registriert: Mo Okt 29, 2018 3:46 pm

Re: Apply auf alle Gruppen eines Dataframes anwenden

Beitrag von AlexWue »

bigben hat geschrieben: Mo Jan 28, 2019 2:09 pm Und wenn wir schon dabei sind, dann schreibe möglichst auch niemals `df <-`.

df ist in R der Name einer Funktion. Damit kann man die F-Verteilung abfragen. Wenn man Werte in Variablennamen speichert, die zugleich Funktionsnamen sind, funktioniert erstmal alles, wie gewohnt. Wenn man aber einen Fehler macht, werden die Fehlermeldungen rasch unbrauchbar.

Das von mir angesprochene Problem ist nicht so gefährlich wie der Gebrauch von `<<-`, aber man sollte sich von Anfang an angewöhnen, keinen data.frame `df` zu nennen. `d.f` ist dagegen völlig in Ordnung.

LG,
Bernhard

alles klar, danke auch hierfür!

Grüße
Alex
jogo
Beiträge: 2085
Registriert: Fr Okt 07, 2016 8:25 am

Re: Apply auf alle Gruppen eines Dataframes anwenden

Beitrag von jogo »

Es könnte etwa so aussehen:

Code: Alles auswählen

volumen_kiste <- 100
Df <- data.frame(volumen_teil = c(20, 50, 90, 20, 30, 10, 10, 80, 10, 20, 50),
                 id_teil = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
                 gruppe = c(1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2))

packeKisten <- function(Vol) {
  if (any(Vol>volumen_kiste)) { message("ein Teil ist zu groß"); stop() }
  Kiste <- integer(length(Vol))
  k <- 1; istart <- 0
  while (length(Vol)) {
    i <- which(cumsum(Vol) <= volumen_kiste)
    Kiste[i+istart] <- k
    Vol <- Vol[-i]; istart <- istart+length(i); k <- k+1   ### nächste Kiste
  }
  Kiste
}

Df$Kiste <- packeKisten(Df$volumen_teil)

# Gruppenhaltigkeit:
Df$Kiste <- ave(Df$volumen_teil, Df$gruppe, FUN=packeKisten)
Gruß, Jörg
p.s.: Den Dataframe habe ich Df genannt.
bigben
Beiträge: 2771
Registriert: Mi Okt 12, 2016 9:09 am

Re: Apply auf alle Gruppen eines Dataframes anwenden

Beitrag von bigben »

Hallo Alex,

Deine Frage nach Optimierung ist einerseits ein wenig schwer zu verstehen, da der Code, den Du da schreibst für sich wenig Sinn ergibt. Er befüllt maximal eine Kiste, der zu optimierende sollte wohl mehrere Kisten befüllen.
Was meinst du mit "die Schleife ist zu langsam"? Wenn sie wirklich so langsam ist, dass das Rückfragen in einem Forum und die ganze Zeit, die Du hier investierst durch schnelleren Code ausgeglichen werden soll, dann haben wir es mutmaßlich mit massiven Datenmengen zu tun? Bei massiven Datenmengen macht es in der Regel Sinn, darüber nachzudenken, was man schon vorher über die Daten weiß. Werden es unsäglich viele Teile sein, die in nur wenige Gruppen zerfallen? Oder sind es fast soviele Gruppen wie Teile?

Du verwendest den logischen Operator `&` zum Verketten von Funktionen. Das entspricht nicht seiner Funktion, das macht den Code unleserlich und das wird ihn auch durch die unnötigen Funktionsaufrufe ein wenig verlangsamen. Besser wäre es, das mit Blöcken zu regeln:

Code: Alles auswählen

volumen_teil <- c(20, 50, 90, 20, 30, 10, 10, 80, 10, 20, 50)
id_teil <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
gruppe <- c(1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2)
d.f <- data.frame(id_teil, gruppe, volumen_teil)

volumen_kiste = 100
id_kiste = 1

zuteilung <- function(d.f){
  for (i in 1:nrow(d.f)){
    ifelse((d.f$volumen_teil[i] <= volumen_kiste), 
           yes = {
                     d.f$id_kiste[i] <- id_kiste 
                     volumen_kiste <- volumen_kiste - d.f$volumen_teil[i]
                  },
           no = {
                    id_kiste <- id_kiste +1
                    d.f$id_kiste[i] <- id_kiste
                    volumen_kiste <- (100 - volumen_teil[i])
                })
  }
  return(d.f)
}
d.f <- zuteilung(df)
Das hier springt sofort ins Auge, bringt aber wahrscheinlich keinen Geschwindigkeitsgewinn:

Code: Alles auswählen

df$id_kiste[i] <- id_kiste
df$id_kiste solltest Du vorher einmal initialisieren, sonst passieren Dinge, mit denen Du nicht gerechnet hast. Schau mal hier:

Code: Alles auswählen

bsp_df_1 <- data.frame(a=1:10, b=1:10)
bsp_df_1$c[1] <- 5
Wieviele Zuweisungen wurden hier gemacht? Was denkst Du, wie

Code: Alles auswählen

bsp_df_1
jetzt aussieht? Würde Dich folgende Zuweisung nicht vor deutlich weniger Probleme stellen?

Code: Alles auswählen

bsp_df_2 <- data.frame(a=1:10, b=1:10)
bsp:df_2 <- NA
bsp_df_2$c[1] <- 5
LG,
Bernhard
---
Programmiere stets so, dass die Maxime Deines Programmierstils Grundlage allgemeiner Gesetzgebung sein könnte
jogo
Beiträge: 2085
Registriert: Fr Okt 07, 2016 8:25 am

Re: Apply auf alle Gruppen eines Dataframes anwenden

Beitrag von jogo »

Eine Optimierung im Sinne von geringst mögliche Anzahl von Kisten pro Gruppe ist ein recht ordentliches Problem der Zahlentheorie.

Hier ist noch eine Variante meiner Funktion:

Code: Alles auswählen

packeKisten2 <- function(Vol) {
  if (any(Vol>volumen_kiste)) { message("ein Teil ist zu groß"); stop() }
  n <- length(Vol)
  Kiste <- integer(n)
  ksum <- 0; k <- 1
  for (i in 1:n) {
    Kiste[i] <- k; ksum <- ksum+Vol[i]
    if (i==n) break
    if ((ksum + Vol[i+1]) > volumen_kiste) { k <- k+1; ksum <- 0} ## neue Kiste
  }
  Kiste
}
Übrigens: ifelse() gehört zu den langsamen Funktionen (besonders, wenn man gar nicht mit Vektoren arbeitet).

Gruß, Jörg
AlexWue
Beiträge: 9
Registriert: Mo Okt 29, 2018 3:46 pm

Re: Apply auf alle Gruppen eines Dataframes anwenden

Beitrag von AlexWue »

bigben hat geschrieben: Mo Jan 28, 2019 2:56 pm Hallo Alex,

Deine Frage nach Optimierung ist einerseits ein wenig schwer zu verstehen, da der Code, den Du da schreibst für sich wenig Sinn ergibt. Er befüllt maximal eine Kiste, der zu optimierende sollte wohl mehrere Kisten befüllen.
Was meinst du mit "die Schleife ist zu langsam"? Wenn sie wirklich so langsam ist, dass das Rückfragen in einem Forum und die ganze Zeit, die Du hier investierst durch schnelleren Code ausgeglichen werden soll, dann haben wir es mutmaßlich mit massiven Datenmengen zu tun? Bei massiven Datenmengen macht es in der Regel Sinn, darüber nachzudenken, was man schon vorher über die Daten weiß. Werden es unsäglich viele Teile sein, die in nur wenige Gruppen zerfallen? Oder sind es fast soviele Gruppen wie Teile?

Du verwendest den logischen Operator `&` zum Verketten von Funktionen. Das entspricht nicht seiner Funktion, das macht den Code unleserlich und das wird ihn auch durch die unnötigen Funktionsaufrufe ein wenig verlangsamen. Besser wäre es, das mit Blöcken zu regeln:

Code: Alles auswählen

volumen_teil <- c(20, 50, 90, 20, 30, 10, 10, 80, 10, 20, 50)
id_teil <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
gruppe <- c(1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2)
d.f <- data.frame(id_teil, gruppe, volumen_teil)

volumen_kiste = 100
id_kiste = 1

zuteilung <- function(d.f){
  for (i in 1:nrow(d.f)){
    ifelse((d.f$volumen_teil[i] <= volumen_kiste), 
           yes = {
                     d.f$id_kiste[i] <- id_kiste 
                     volumen_kiste <- volumen_kiste - d.f$volumen_teil[i]
                  },
           no = {
                    id_kiste <- id_kiste +1
                    d.f$id_kiste[i] <- id_kiste
                    volumen_kiste <- (100 - volumen_teil[i])
                })
  }
  return(d.f)
}
d.f <- zuteilung(df)
Das hier springt sofort ins Auge, bringt aber wahrscheinlich keinen Geschwindigkeitsgewinn:

Code: Alles auswählen

df$id_kiste[i] <- id_kiste
df$id_kiste solltest Du vorher einmal initialisieren, sonst passieren Dinge, mit denen Du nicht gerechnet hast. Schau mal hier:

Code: Alles auswählen

bsp_df_1 <- data.frame(a=1:10, b=1:10)
bsp_df_1$c[1] <- 5
Wieviele Zuweisungen wurden hier gemacht? Was denkst Du, wie

Code: Alles auswählen

bsp_df_1
jetzt aussieht? Würde Dich folgende Zuweisung nicht vor deutlich weniger Probleme stellen?

Code: Alles auswählen

bsp_df_2 <- data.frame(a=1:10, b=1:10)
bsp:df_2 <- NA
bsp_df_2$c[1] <- 5
LG,
Bernhard

nein, der Code liefert mir das gewünschte Ergebnis, nur soll die Rechenzeit noch verkürzt werden.
Der schneller Code gleicht nicht meinen Aufwand aus, ich mache das für eine Uni-Arbeit...

danke für den Hinweis mit dem initialisieren und der besseren Lesbarkeit!

jogo hat geschrieben: Mo Jan 28, 2019 2:44 pm Es könnte etwa so aussehen:

Code: Alles auswählen

volumen_kiste <- 100
Df <- data.frame(volumen_teil = c(20, 50, 90, 20, 30, 10, 10, 80, 10, 20, 50),
                 id_teil = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
                 gruppe = c(1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2))

packeKisten <- function(Vol) {
  if (any(Vol>volumen_kiste)) { message("ein Teil ist zu groß"); stop() }
  Kiste <- integer(length(Vol))
  k <- 1; istart <- 0
  while (length(Vol)) {
    i <- which(cumsum(Vol) <= volumen_kiste)
    Kiste[i+istart] <- k
    Vol <- Vol[-i]; istart <- istart+length(i); k <- k+1   ### nächste Kiste
  }
  Kiste
}

Df$Kiste <- packeKisten(Df$volumen_teil)

# Gruppenhaltigkeit:
Df$Kiste <- ave(Df$volumen_teil, Df$gruppe, FUN=packeKisten)
Gruß, Jörg
p.s.: Den Dataframe habe ich Df genannt.

das funktioniert, ist aber leider langsamer als mein aktueller Code. Dennoch danke!

Mit den "apply" Packages habt ihr nicht zufällig Erfahrung?


Viele Grüße
Alex
jogo
Beiträge: 2085
Registriert: Fr Okt 07, 2016 8:25 am

Re: Apply auf alle Gruppen eines Dataframes anwenden

Beitrag von jogo »

Hallo Alex,

kannst Du bitte mal schreiben, wieviele Zeilen Dein Dataframe hat. Wieviele Zeilen sind durchschnittlich in einer Gruppe?
Bei meiner ersten Variante ist cumsum() der Bremser, wenn der Vektor sehr lang ist und die Anzahl der Teile pro Kiste klein.

Gruß, Jörg
AlexWue hat geschrieben: Mo Jan 28, 2019 3:52 pm das funktioniert, ist aber leider langsamer als mein aktueller Code. Dennoch danke!

Mit den "apply" Packages habt ihr nicht zufällig Erfahrung?
Antworten