122  練習問題の解答

できましたか?

これまでの知識も利用しながら実施する問題なので少し難しかったかもしれません。

Q0: data/stringr.xlsxファイルのSheet1にあるデータをよみこみましょう。

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.2     ✔ readr     2.1.4
✔ forcats   1.0.0     ✔ stringr   1.5.0
✔ ggplot2   3.4.2     ✔ tibble    3.2.1
✔ lubridate 1.9.2     ✔ tidyr     1.3.0
✔ purrr     1.0.1     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(readxl)
dat <- read_excel("data/stringr.xlsx")
dat
# A tibble: 6 × 3
  target1            target2        target3           
  <chr>              <chr>          <chr>             
1 abc:500ml 1unit    AST 50IU,      ope:A 4.5hr 8Oml  
2 def 250ml 4units   HbA1c 5.0%     ope:B 3hr 10ml    
3 ghi  100ml 5units  BMI 23.1kg/m^2 ope:C 12.5hr 1OOml
4 jkl  100ml 6units  AST: 60IU      ope:D 4.5hr 180ml 
5 jkl  100ml 9units  HbA1c 5.0%     ope:E 3hr 120ml   
6 abc  100ml 20units BMI 18.1kg/m^2 ope:F 12.5hr 1OOml

これは、特に問題ありませんね。これまで通りです。

Q1-1 target1列のみに列を絞って、“xxx100ml 1unit”の、unitの前の数字をぬきだして新しい列、unitを作成してください

dat1 <- dat %>% 
  select(target1) %>% 
  mutate(unit = str_extract(target1, "\\d+(?=unit)"))

dat1
# A tibble: 6 × 2
  target1            unit 
  <chr>              <chr>
1 abc:500ml 1unit    1    
2 def 250ml 4units   4    
3 ghi  100ml 5units  5    
4 jkl  100ml 6units  6    
5 jkl  100ml 9units  9    
6 abc  100ml 20units 20   

これも、基本的なextract関数の使い方で、正規表現そのものもそれほど複雑ではないですね?

Q1-2 Q1-1で作成した表で、target1列の”xxx100ml 1unit”の、ml前の数字をぬきだして、mlではなくてL単位に変換(100mlなら0.1L)してlitterという新しい列を追加してください。

dat1 <- dat1 %>% 
  mutate(ml = str_extract(target1,"\\d+(?=ml)"))

dat1 <- dat1 %>% 
  mutate(litter = as.numeric(ml)/1000)

dat1
# A tibble: 6 × 4
  target1            unit  ml    litter
  <chr>              <chr> <chr>  <dbl>
1 abc:500ml 1unit    1     500     0.5 
2 def 250ml 4units   4     250     0.25
3 ghi  100ml 5units  5     100     0.1 
4 jkl  100ml 6units  6     100     0.1 
5 jkl  100ml 9units  9     100     0.1 
6 abc  100ml 20units 20    100     0.1 

すこし込み入ってきました。as.numericで文字列を数字型に変換してあげれば計算することができます。

Q1-3 Q1-2で作成した表で、target1の”xxx100ml 2unit”の、xxx部分を抜き出して、nameという名前の新しい列を作成してください。尚前後に余分なスペースがあればname列から削除してください。

最初の正規表現的な難問です。

dat1 <- dat1 %>% 
  mutate(name = str_extract(target1,"^.+(?=(:| ))"))

dat1
# A tibble: 6 × 5
  target1            unit  ml    litter name      
  <chr>              <chr> <chr>  <dbl> <chr>     
1 abc:500ml 1unit    1     500     0.5  abc:500ml 
2 def 250ml 4units   4     250     0.25 def 250ml 
3 ghi  100ml 5units  5     100     0.1  ghi  100ml
4 jkl  100ml 6units  6     100     0.1  jkl  100ml
5 jkl  100ml 9units  9     100     0.1  jkl  100ml
6 abc  100ml 20units 20    100     0.1  abc  100ml

このように、Look Aroundでスペースかコロンを指定しても、後ろのスペースのところまでを.+が拾ってしまいます。

こういう場合は、正規表現のところで解説した.+?という+のマッチする範囲を最小に絞るという表記を利用しましょう。

dat1 <- dat1 %>% 
  mutate(name = str_extract(target1,"^.+?(?=(:| ))"))

dat1
# A tibble: 6 × 5
  target1            unit  ml    litter name 
  <chr>              <chr> <chr>  <dbl> <chr>
1 abc:500ml 1unit    1     500     0.5  abc  
2 def 250ml 4units   4     250     0.25 def  
3 ghi  100ml 5units  5     100     0.1  ghi  
4 jkl  100ml 6units  6     100     0.1  jkl  
5 jkl  100ml 9units  9     100     0.1  jkl  
6 abc  100ml 20units 20    100     0.1  abc  

できました!

Q1-4 Q1-3で作成した表で、target1列を削除して、name, litter, unitの順に列の順番を並べてください

dat1
# A tibble: 6 × 5
  target1            unit  ml    litter name 
  <chr>              <chr> <chr>  <dbl> <chr>
1 abc:500ml 1unit    1     500     0.5  abc  
2 def 250ml 4units   4     250     0.25 def  
3 ghi  100ml 5units  5     100     0.1  ghi  
4 jkl  100ml 6units  6     100     0.1  jkl  
5 jkl  100ml 9units  9     100     0.1  jkl  
6 abc  100ml 20units 20    100     0.1  abc  
colnames(dat1)
[1] "target1" "unit"    "ml"      "litter"  "name"   

から、必要なものだけを選べばよいので、問題文通りにするのであれば、

dat1 %>% 
  select(!c(target1, ml)) %>% 
  select(name, litter, unit)
# A tibble: 6 × 3
  name  litter unit 
  <chr>  <dbl> <chr>
1 abc     0.5  1    
2 def     0.25 4    
3 ghi     0.1  5    
4 jkl     0.1  6    
5 jkl     0.1  9    
6 abc     0.1  20   

ですが、selectはそもそもそれ以外削除するので、

dat1 %>% select(name,litter,unit)
# A tibble: 6 × 3
  name  litter unit 
  <chr>  <dbl> <chr>
1 abc     0.5  1    
2 def     0.25 4    
3 ghi     0.1  5    
4 jkl     0.1  6    
5 jkl     0.1  9    
6 abc     0.1  20   

でOKです。

Q2-1 target2列のみに絞って、検査結果のみを抜き出して新しいValue列を作成してください

dat2 <- dat %>% select(target2)
dat2
# A tibble: 6 × 1
  target2       
  <chr>         
1 AST 50IU,     
2 HbA1c 5.0%    
3 BMI 23.1kg/m^2
4 AST: 60IU     
5 HbA1c 5.0%    
6 BMI 18.1kg/m^2
dat2 <- dat2 %>% 
  mutate(value = str_extract(target2,"(?<=\\s)(\\d+\\.\\d+|\\d+)"))

Q2-2 Q2-1で作成した表に、target2列の単位を抜き出して新しい列、unitを作成してください。

正規表現で直接抜き出すのは難しいのでstr_replaceを使いましょう

dat2 <- dat2 %>% 
  mutate(num_unit = str_extract(target2, "(?<=\\s).+$")) %>% 
  mutate(unit = str_replace(num_unit,"\\d+\\.\\d+|\\d+",""))
dat2
# A tibble: 6 × 4
  target2        value num_unit   unit  
  <chr>          <chr> <chr>      <chr> 
1 AST 50IU,      50    50IU,      IU,   
2 HbA1c 5.0%     5.0   5.0%       %     
3 BMI 23.1kg/m^2 23.1  23.1kg/m^2 kg/m^2
4 AST: 60IU      60    60IU       IU    
5 HbA1c 5.0%     5.0   5.0%       %     
6 BMI 18.1kg/m^2 18.1  18.1kg/m^2 kg/m^2

なぜか1行目のIUの最後にカンマがありますね(これは本気で私が消し忘れていたカンマです。ついでにこれをけしておきましょう。)

dat2 <- dat2 %>% 
  mutate(unit = str_replace(unit,",$",""))
dat2
# A tibble: 6 × 4
  target2        value num_unit   unit  
  <chr>          <chr> <chr>      <chr> 
1 AST 50IU,      50    50IU,      IU    
2 HbA1c 5.0%     5.0   5.0%       %     
3 BMI 23.1kg/m^2 23.1  23.1kg/m^2 kg/m^2
4 AST: 60IU      60    60IU       IU    
5 HbA1c 5.0%     5.0   5.0%       %     
6 BMI 18.1kg/m^2 18.1  18.1kg/m^2 kg/m^2

Q2-3 Q2-2で作成した表に、target2列の検査名を抜き出して、新しい列、nameを作成してください。

dat2 <- dat2 %>% 
  mutate(name = str_extract(target2,".+?(?=(:|\\s))"))
dat2
# A tibble: 6 × 5
  target2        value num_unit   unit   name 
  <chr>          <chr> <chr>      <chr>  <chr>
1 AST 50IU,      50    50IU,      IU     AST  
2 HbA1c 5.0%     5.0   5.0%       %      HbA1c
3 BMI 23.1kg/m^2 23.1  23.1kg/m^2 kg/m^2 BMI  
4 AST: 60IU      60    60IU       IU     AST  
5 HbA1c 5.0%     5.0   5.0%       %      HbA1c
6 BMI 18.1kg/m^2 18.1  18.1kg/m^2 kg/m^2 BMI  

これも.+?とつけないと、:を省いて抜き出すことができませんね

Q2-4 Q2-3で作成した表の、target2列を削除して、name,value,unitの順番に列を並び替えてください

dat2 %>% select(name, value, unit)
# A tibble: 6 × 3
  name  value unit  
  <chr> <chr> <chr> 
1 AST   50    IU    
2 HbA1c 5.0   %     
3 BMI   23.1  kg/m^2
4 AST   60    IU    
5 HbA1c 5.0   %     
6 BMI   18.1  kg/m^2

Q3-1 target3列のみに列を絞って、手術名(op:XX)を抜き出して新しい列nameを作成してください

dat3 <- dat %>% 
  select(target3) %>% 
  mutate(name = str_extract(target3,"(?<=ope:).+?(?= \\d)"))

Q3-2 Q3-1で作成した表の、 target3列のの出血量をぬきだして新しい列、blood_lostを作成してください。今までと同じ方法ではうまくいきませんがなぜでしょうか?元のデータは触らずに、本来入力したかったであろう数値に置き換えてください。

これではうまくいきません

dat3 %>% 
  mutate(blood_lost = str_extract(target3,"\\d+(?=ml)"))
# A tibble: 6 × 3
  target3            name  blood_lost
  <chr>              <chr> <chr>     
1 ope:A 4.5hr 8Oml   A     <NA>      
2 ope:B 3hr 10ml     B     10        
3 ope:C 12.5hr 1OOml C     <NA>      
4 ope:D 4.5hr 180ml  D     180       
5 ope:E 3hr 120ml    E     120       
6 ope:F 12.5hr 1OOml F     <NA>      

実は

dat3$target3 %>% 
  str_view("O") #大文字のオー
[1] │ ope:A 4.5hr 8<O>ml
[3] │ ope:C 12.5hr 1<O><O>ml
[6] │ ope:F 12.5hr 1<O><O>ml

という風に、一部のゼロが大文字のオーに置き換わっています。、

dat3 %>% 
  mutate(blood_lost = str_extract(target3,"(?<=hr\\s).+(?=ml)")) %>% 
  mutate(blood_lost = str_replace(blood_lost,"O","0"))
# A tibble: 6 × 3
  target3            name  blood_lost
  <chr>              <chr> <chr>     
1 ope:A 4.5hr 8Oml   A     80        
2 ope:B 3hr 10ml     B     10        
3 ope:C 12.5hr 1OOml C     10O       
4 ope:D 4.5hr 180ml  D     180       
5 ope:E 3hr 120ml    E     120       
6 ope:F 12.5hr 1OOml F     10O       

抜き出してから大文字のオーを、数字のゼロに置き換えます。ただし、オーが二つあるデータもあるので、一回だけでは全部置き換えられません。

dat3 %>% 
  mutate(blood_lost = str_extract(target3,"(?<=hr\\s).+(?=ml)")) %>% 
  mutate(blood_lost = str_replace(blood_lost,"O","0")) %>% 
  mutate(blood_lost = str_replace(blood_lost,"O","0"))
# A tibble: 6 × 3
  target3            name  blood_lost
  <chr>              <chr> <chr>     
1 ope:A 4.5hr 8Oml   A     80        
2 ope:B 3hr 10ml     B     10        
3 ope:C 12.5hr 1OOml C     100       
4 ope:D 4.5hr 180ml  D     180       
5 ope:E 3hr 120ml    E     120       
6 ope:F 12.5hr 1OOml F     100       

このように、2回置き換えてもよいですが、それだと何回置き換えてよいかわからないケースも多いので、全部置き換えるstr_replace_allを利用しましょう。

dat3 <- dat3 %>% 
  mutate(blood_lost = str_extract(target3,"(?<=hr\\s).+(?=ml)")) %>% 
  mutate(blood_lost = str_replace_all(blood_lost,"O","0"))
  
dat3
# A tibble: 6 × 3
  target3            name  blood_lost
  <chr>              <chr> <chr>     
1 ope:A 4.5hr 8Oml   A     80        
2 ope:B 3hr 10ml     B     10        
3 ope:C 12.5hr 1OOml C     100       
4 ope:D 4.5hr 180ml  D     180       
5 ope:E 3hr 120ml    E     120       
6 ope:F 12.5hr 1OOml F     100       

この_allが作関数他にもあって

str_view_all(dat3$target3,"O")
Warning: `str_view()` was deprecated in stringr 1.5.0.
ℹ Please use `str_view_all()` instead.
[1] │ ope:A 4.5hr 8<O>ml
[2] │ ope:B 3hr 10ml
[3] │ ope:C 12.5hr 1<O><O>ml
[4] │ ope:D 4.5hr 180ml
[5] │ ope:E 3hr 120ml
[6] │ ope:F 12.5hr 1<O><O>ml
str_extract_all(c("abc","aba","abcba"), "a")
[[1]]
[1] "a"

[[2]]
[1] "a" "a"

[[3]]
[1] "a" "a"

こんな感じでviewやextractにもあります。extractの結果はListというもので、まだ解説していませんので、わからなくて結構です。

また、置き換えるときに、複数個置き換えたいばあいは、str_replace_allを利用すると便利です。

Q3-3 Q3-2で作成した表の、target3列のhr前の時間を抜き出して新しい列、time_hrを作成してください。

dat3 <- dat3 %>% 
  mutate(time_hr = str_extract(target3,"(?<=\\s)(\\d+\\.\\d+|\\d+)(?=hr)"))

Q3-4 Q3-3で作成した表の、target3列を削除して、name, blood_lost, time_hrの順番に並び替えてください。

dat3 %>% 
  select(name, blood_lost, time_hr)
# A tibble: 6 × 3
  name  blood_lost time_hr
  <chr> <chr>      <chr>  
1 A     80         4.5    
2 B     10         3      
3 C     100        12.5   
4 D     180        4.5    
5 E     120        3      
6 F     100        12.5   

以上でstringrの練習問題の解説終了です!