如何标记数据框中第一次出现的字符串以及之后的所有行?
How to flag the first occurrence of a character string in a data frame and all rows after?
假设我们从如下所示的数据框开始:
ID STATE_1 STATE_2
1 1 NULL NULL
2 1 FRY NULL
3 1 NULL CRY
4 1 FRY CRY
5 1 NULL NULL
6 1 NULL NULL
7 1 FRY CRY
8 1 NULL NULL
9 5 NULL NULL
10 5 NULL CRY
11 5 FRY NULL
12 5 NULL NULL
复制如下:
ID <- c(1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5)
STATE_1 <- c("NULL", "FRY", "NULL", "FRY", "NULL", "NULL", "FRY", "NULL", "NULL", "NULL", "FRY", "NULL")
STATE_2 <- c("NULL", "NULL", "CRY", "CRY", "NULL", "NULL", "CRY", "NULL", "NULL", "CRY", "NULL", "NULL")
df <- data.frame(ID, STATE_1, STATE_2)
如何添加一个 FLAG 列,从而对于每个 ID,一旦多个状态中的第一个被非 NULL 元素触发,则该 ID 的所有后续行都标记为第一个非 NULL状态?因此,例如,添加 FLAG 列后,输出将如下所示:
ID STATE_1 STATE_2 FLAG [Explanations for FLAG column elements]
1 1 NULL NULL NULL No flag tripped for ID 1 (yet) so value is NULL
2 1 FRY NULL FRY FRY tripped in STATE_1 so FRY applies for all subsequent rows for this ID
3 1 NULL CRY FRY Ignore CRY since FRY happened first
4 1 FRY CRY FRY
5 1 NULL NULL FRY
6 1 NULL NULL FRY
7 1 FRY CRY FRY
8 1 NULL NULL FRY
9 5 NULL NULL NULL No flag tripped for ID 5 (yet) so value is NULL
10 5 NULL CRY CRY CRY tripped in STATE_2 so CRY applies for all subsequent rows for this ID
11 5 FRY NULL CRY Ignore FRY since CRY happened first
12 5 NULL NULL CRY But please read the following paragraph, because another possible scenario is omitted for sake of brevity
如果在上面的第 10 行中,对于 ID 5,同一行中同时出现了 FRY 和 CRY,那么 FRY 将控制所有后续的 ID 5 行。我没有在上面的示例中包括这种情况。所以有一个 STATE_1 胜过 STATE_2 的优先级,等等
在我的实际数据中有6个状态,而这个例子中有2个状态(STATE_1和STATE_2)。
我非常喜欢与 dplyr()
一起工作。
当我对此进行研究时,有几篇文章针对基于数字触发器的标记数据,但我找不到像我的数据中那样使用字符串触发器的任何解决方案。我希望有一个字符串解决方案,这样我就可以避免从字符串转换为数字变量的额外步骤。
是的,这可以用dplyr
和字符串来完成。这是一种方法:
df %>%
mutate(across(.cols = c("STATE_1", "STATE_2"), ~ na_if(., "NULL"))) %>%
group_by(ID) %>%
fill(STATE_1, STATE_2, .direction = "down") %>%
mutate(flag = if_else(is.na(lag(coalesce(STATE_1, STATE_2))),
coalesce(STATE_1, STATE_2),
NA_character_)) %>%
fill(flag, .direction = "down")
ID STATE_1 STATE_2 flag
1 1 <NA> <NA> <NA>
2 1 FRY <NA> FRY
3 1 FRY CRY FRY
4 1 FRY CRY FRY
5 1 FRY CRY FRY
6 1 FRY CRY FRY
7 1 FRY CRY FRY
8 1 FRY CRY FRY
9 5 <NA> <NA> <NA>
10 5 <NA> CRY CRY
11 5 FRY CRY CRY
12 5 FRY CRY CRY
一步一步,这是这个解决方案的作用:
- (将字符“NULL”值转换为
NA
。)
- Group by
ID
以便我们在每个ID内单独操作。
- 使用
fill()
使用STATE_1
和STATE_2
中的每个非空值来填充它下面的所有非空值,直到我们到达下一个非空值. (您也可以填写“向上”而不是向下;向下是默认方向,但为了清楚起见,我已将其明确包含在内。)
- 创建
flag
字段。如果前一行中的两个状态都有空值(由lag()
标识),那么我们想要触发一个新标志;使用 coalesce()
使 STATE_1
优先于 STATE_2
。否则,我们不想要一面新旗帜;现在用 NA
填充。
- 再次使用
fill()
向下填充标志:每个新标志都会填充其下方的行,直到我们得到一个新标志。
此过程也适用于您描述的场景,其中第 10 行有 STATE_1
的“FRY”:
df2 = df
df2$STATE_1[10] = "FRY"
df2 %>%
mutate(across(.cols = -c("ID"), ~ na_if(., "NULL"))) %>%
group_by(ID) %>%
fill(STATE_1, STATE_2, .direction = "down") %>%
mutate(flag = if_else(is.na(lag(coalesce(STATE_1, STATE_2))),
coalesce(STATE_1, STATE_2),
NA_character_)) %>%
fill(flag, .direction = "down")
ID STATE_1 STATE_2 flag
1 1 <NA> <NA> <NA>
2 1 FRY <NA> FRY
3 1 FRY CRY FRY
4 1 FRY CRY FRY
5 1 FRY CRY FRY
6 1 FRY CRY FRY
7 1 FRY CRY FRY
8 1 FRY CRY FRY
9 5 <NA> <NA> <NA>
10 5 FRY CRY FRY
11 5 FRY CRY FRY
12 5 FRY CRY FRY
假设我们从如下所示的数据框开始:
ID STATE_1 STATE_2
1 1 NULL NULL
2 1 FRY NULL
3 1 NULL CRY
4 1 FRY CRY
5 1 NULL NULL
6 1 NULL NULL
7 1 FRY CRY
8 1 NULL NULL
9 5 NULL NULL
10 5 NULL CRY
11 5 FRY NULL
12 5 NULL NULL
复制如下:
ID <- c(1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5)
STATE_1 <- c("NULL", "FRY", "NULL", "FRY", "NULL", "NULL", "FRY", "NULL", "NULL", "NULL", "FRY", "NULL")
STATE_2 <- c("NULL", "NULL", "CRY", "CRY", "NULL", "NULL", "CRY", "NULL", "NULL", "CRY", "NULL", "NULL")
df <- data.frame(ID, STATE_1, STATE_2)
如何添加一个 FLAG 列,从而对于每个 ID,一旦多个状态中的第一个被非 NULL 元素触发,则该 ID 的所有后续行都标记为第一个非 NULL状态?因此,例如,添加 FLAG 列后,输出将如下所示:
ID STATE_1 STATE_2 FLAG [Explanations for FLAG column elements]
1 1 NULL NULL NULL No flag tripped for ID 1 (yet) so value is NULL
2 1 FRY NULL FRY FRY tripped in STATE_1 so FRY applies for all subsequent rows for this ID
3 1 NULL CRY FRY Ignore CRY since FRY happened first
4 1 FRY CRY FRY
5 1 NULL NULL FRY
6 1 NULL NULL FRY
7 1 FRY CRY FRY
8 1 NULL NULL FRY
9 5 NULL NULL NULL No flag tripped for ID 5 (yet) so value is NULL
10 5 NULL CRY CRY CRY tripped in STATE_2 so CRY applies for all subsequent rows for this ID
11 5 FRY NULL CRY Ignore FRY since CRY happened first
12 5 NULL NULL CRY But please read the following paragraph, because another possible scenario is omitted for sake of brevity
如果在上面的第 10 行中,对于 ID 5,同一行中同时出现了 FRY 和 CRY,那么 FRY 将控制所有后续的 ID 5 行。我没有在上面的示例中包括这种情况。所以有一个 STATE_1 胜过 STATE_2 的优先级,等等
在我的实际数据中有6个状态,而这个例子中有2个状态(STATE_1和STATE_2)。
我非常喜欢与 dplyr()
一起工作。
当我对此进行研究时,有几篇文章针对基于数字触发器的标记数据,但我找不到像我的数据中那样使用字符串触发器的任何解决方案。我希望有一个字符串解决方案,这样我就可以避免从字符串转换为数字变量的额外步骤。
是的,这可以用dplyr
和字符串来完成。这是一种方法:
df %>%
mutate(across(.cols = c("STATE_1", "STATE_2"), ~ na_if(., "NULL"))) %>%
group_by(ID) %>%
fill(STATE_1, STATE_2, .direction = "down") %>%
mutate(flag = if_else(is.na(lag(coalesce(STATE_1, STATE_2))),
coalesce(STATE_1, STATE_2),
NA_character_)) %>%
fill(flag, .direction = "down")
ID STATE_1 STATE_2 flag
1 1 <NA> <NA> <NA>
2 1 FRY <NA> FRY
3 1 FRY CRY FRY
4 1 FRY CRY FRY
5 1 FRY CRY FRY
6 1 FRY CRY FRY
7 1 FRY CRY FRY
8 1 FRY CRY FRY
9 5 <NA> <NA> <NA>
10 5 <NA> CRY CRY
11 5 FRY CRY CRY
12 5 FRY CRY CRY
一步一步,这是这个解决方案的作用:
- (将字符“NULL”值转换为
NA
。) - Group by
ID
以便我们在每个ID内单独操作。 - 使用
fill()
使用STATE_1
和STATE_2
中的每个非空值来填充它下面的所有非空值,直到我们到达下一个非空值. (您也可以填写“向上”而不是向下;向下是默认方向,但为了清楚起见,我已将其明确包含在内。) - 创建
flag
字段。如果前一行中的两个状态都有空值(由lag()
标识),那么我们想要触发一个新标志;使用coalesce()
使STATE_1
优先于STATE_2
。否则,我们不想要一面新旗帜;现在用NA
填充。 - 再次使用
fill()
向下填充标志:每个新标志都会填充其下方的行,直到我们得到一个新标志。
此过程也适用于您描述的场景,其中第 10 行有 STATE_1
的“FRY”:
df2 = df
df2$STATE_1[10] = "FRY"
df2 %>%
mutate(across(.cols = -c("ID"), ~ na_if(., "NULL"))) %>%
group_by(ID) %>%
fill(STATE_1, STATE_2, .direction = "down") %>%
mutate(flag = if_else(is.na(lag(coalesce(STATE_1, STATE_2))),
coalesce(STATE_1, STATE_2),
NA_character_)) %>%
fill(flag, .direction = "down")
ID STATE_1 STATE_2 flag
1 1 <NA> <NA> <NA>
2 1 FRY <NA> FRY
3 1 FRY CRY FRY
4 1 FRY CRY FRY
5 1 FRY CRY FRY
6 1 FRY CRY FRY
7 1 FRY CRY FRY
8 1 FRY CRY FRY
9 5 <NA> <NA> <NA>
10 5 FRY CRY FRY
11 5 FRY CRY FRY
12 5 FRY CRY FRY