Penggunaan R untuk tugas utilitas

Alat yang bagus + ketersediaan keterampilan untuk bekerja dengannya, yang dicapai melalui latihan, memungkinkan Anda untuk dengan mudah dan elegan menyelesaikan banyak tugas "suka" yang berbeda. Di bawah ini adalah beberapa contoh serupa. Saya yakin banyak yang dapat memperluas daftar ini.


Ini adalah kelanjutan dari publikasi sebelumnya .


Analisis Log Aplikasi


Cukup populer adalah tugas melakukan perhitungan analitik berdasarkan log aplikasi. Misalnya, melakukan analisis tindakan pengguna dan memperkirakan indikator perkiraan, atau menguji hipotesis. Anda dapat mengikuti versi klasik dan meningkatkan tumpukan ELK atau sejenisnya (baru-baru ini, Splunk telah keluar dari sistem yang tersedia di Rusia). Tetapi Anda dapat berpikir sedikit dan cepat melakukan semuanya pada R. Dengan cepat dalam segala hal, baik dalam implementasi maupun dalam pemrosesan waktu.


Tetapi ada sejumlah fitur saat memecahkan masalah yang sama:


  1. Biasanya, file log ditulis dalam format log4j klasik: cap waktu, kepentingan, jenis subsistem, isi pesan.
  2. Stempel waktu dapat berisi peristiwa dengan resolusi milidetik, yang harus dipertahankan untuk keakuratan analitik berikutnya. Milidetik dapat menulis tanpa mematuhi ISO 8601.
  3. Isi pesan adalah entitas yang praktis tidak terstruktur. Pengembang menulis semua yang mereka anggap perlu di sana, tanpa membatasi diri pada format presentasi apa pun.
  4. Kadang-kadang badan pesan multi-line, misalnya, output dari tumpukan panggilan java, atau paket pertukaran intersistem xml. Hal ini diperlukan untuk merekonstruksi rekaman multi-baris menjadi satu (penanda timestamp adalah tanda awal rekaman).
  5. Sejumlah atriutes dapat berupa eksternal untuk konten dan mereka harus diperoleh dengan cara yang berbeda, misalnya, id objek dapat dikodekan dalam nama file log.
  6. Log dalam bentuk file bisa beberapa megabita atau ratusan gigabita.
  7. Tugasnya paralel dengan sangat baik.

Bahkan, tugas dapat dibagi menjadi 2 langkah:


  • preprocessing data mentah;
  • analisis selanjutnya.

Isi dari langkah terakhir ditentukan oleh area subjek dan tugas-tugas bisnis, R sangat cocok untuk langkah ini. Banyak orang tidak tahu, tetapi langkah pertama juga dapat diselesaikan dengan mudah dengan R. Selain itu, tergantung pada ukuran file log, hasil preprocessing terstruktur sebagian cocok untuk analisis lebih lanjut dapat ditambahkan ke file maupun ke database. Terabyte menggiling satu atau dua.


Hanya kode contoh:
 library(readr) library(tidyverse) library(magrittr) library(stringi) library(fs) library(glue) library(RClickhouse) library(DBI) library(anytime) library(tictoc) library(iterators) library(foreach) library(doParallel) library(futile.logger) library(re2r) library(data.table) library(future) library(doFuture) common_logname <- "DEV_log_parser.log" table_name <- "DEV_LOGS" flog.appender(appender.file(common_logname)) flog.threshold(INFO) flog.info("Start batch processing") oneTimeProcessing <- function(f_iter, log_type = c("app", "system")) { log_type <- match.arg(log_type) checkmate::assertNames(names(f_iter), permutation.of = c("fname", "short_fname", "location", "wk", "size", "id")) cfg <- list(app = list(db_table = "DEV_APP_LOGS"), system = list(db_table = "DEV_LOGS")) #   data <- readr::read_lines(file = f_iter$fname, progress = FALSE) log_df <- setDT(tibble::enframe(data, name = NULL)) %>% .[, log_line_start := re2r::re2_detect(value, pattern = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}", parallel = F)] %>% .[, log_line_number := cumsum(log_line_start)] %>% .[, body := stri_c(value, collapse = "\n"), by = log_line_number] %>% .[, `:=`(value = NULL, log_line_start = NULL, log_line_number = NULL)] %>% tibble::as_tibble() %>% #  body = character(0)      0  #      POSIXct tidyr::extract(col = "body", into = c("timestamp", "tz", "level", "module", "class", "message"), # tz   (  DEV),      ( DEV) regex = "^(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}:\\d+([+-]\\d+)?) (.*?) <(.*?)> \\[(.*?)\\] (?s:(.*))$", case_insensitive = TRUE, ignore.case = TRUE) %>% #     ISO         (   ?) #  ISO 8601 (https://en.wikipedia.org/wiki/ISO_8601) mutate_at("timestamp", re2r::re2_replace, # tz   (  DEV),      ( DEV) pattern = "(.*) (\\d{2}:\\d{2}:\\d{2}):(\\d+([+-]\\d+)?)", replacement = "\\1T\\2.\\3") %>% mutate_at("timestamp", lubridate::as_datetime, tz = "Europe/Moscow") %>% #    mutate(location = f_iter$location, wk = f_iter$wk) # TRUNCATE  CH    ,           #    CH, ms    (timestamp %% 1) conn <- DBI::dbConnect(RClickhouse::clickhouse(), host = "10.0.0.1", db = "DEV_LOGS") # m <- DBI::dbExecute(conn, glue("ALTER TABLE {table_name}")) write_res <- log_df %>% mutate(ms = (as.numeric(timestamp) %% 1) * 1000) %>% select(location, wk, timestamp, ms, level, module, class, message) %>% #            DBI::dbWriteTable(conn, cfg[[log_type]][["db_table"]], ., append = TRUE) DBI::dbDisconnect(conn) #       res <- tibble::tibble(id = f_iter$id, lines = nrow(log_df), min_t = min(log_df$timestamp), max_t = max(log_df$timestamp), write_res) rm(data, log_df) return(res) } #    tic("Batch processing") #    gc(full = TRUE) nworkers <- parallel::detectCores() - 1 registerDoFuture() # future::plan(multiprocess) # future::plan(multisession) future::plan(multisession, workers = nworkers) # future::plan(sequential) #  ~  #      CH #   ------------------ fnames_tbl <- here::here("raw_data") %>% fs::dir_ls(recurse = TRUE, glob = "*dev_app*.gz") %>% enframe(name = "fname") %>% #         mutate(short_fname = as.character(fs::path_rel(fname, start = "./raw_data"))) %>% select(-value) %>% mutate(size = fs::file_size(fname)) %>% tidyr::extract(col = "short_fname", into = c("location", "wk"), regex = "^([^/]+)/wk(\\d+)", remove = FALSE) %>% arrange(size) %>% mutate(id = paste(format(row_number(), justify = "r", width = 4), "/", n())) %>% #   ~ N  mutate(chunk = (row_number() %% nworkers + 1)) %>% #    ,  dopar    arrange(chunk) start_time <- Sys.time() stat_list <- foreach(it = iter(fnames_tbl, by = "row"), .export = c("start_time"), .verbose = TRUE, .inorder = FALSE, .errorhandling = "remove") %dopar% { #   flog.appender(appender.file(common_logname)) # flog.info(capture.output(gc(verbose = TRUE))) res <- oneTimeProcessing(it, log_type = "app") flog.info(glue("Step {it$id} finished.", "Elapsed {round(difftime(Sys.time(), start_time, units = 'mins'), digits = 2)} min(s) ----------->", .sep = " ")) return(res) } flog.info("Load finished") #    -------------- #    ,    future::plan(sequential) gc(reset = TRUE, full = TRUE) flog.info(capture.output(toc())) #     ------------- logstat_tbl <- stat_list %>% dplyr::bind_rows() %>% #    left_join(fnames_tbl, by = "id") %>% #          mutate(delta_t = as.numeric(difftime(max_t, min_t, units = "mins"))) %>% arrange(min_t) write_delim(logstat_tbl, here::here("output", "DEV_parse_stat.csv.gz"), delim = ";") # ,     ? if(nrow(logstat_tbl) < nrow(fnames_tbl)){ flog.error("!!!!!!! Not all workers were executed successfully !!!!!!!!!") } 

Contoh kode ini berisi konsep dasar seperti paralelisasi, waktu pemrosesan dengan mempertimbangkan milidetik, menyimpan ke database, menghitung catatan multi-baris, merangkum hasil kerja, menggunakan atribut eksternal, pembandingan awal dan memilih fungsi dan paket optimal ( re2r , misalnya; ini perpustakaan google untuk bekerja dengan yang biasa adalah yang tercepat dan banyak digunakan di mana, ambil ClickHouse yang sama yang disebutkan dalam kode { bencmark , beberapa operator mungkin memiliki ILV ditutup}). Tetapi kode tidak mengklaim ideal, karena itu hanya tindakan satu kali pada preprocessing data. Itu cepat dan benar, well, ok. Untuk tugas serupa lainnya, kami mengoreksi, dengan mempertimbangkan resp akun. input data.


Apakah akan lebih cepat dalam hal waktu untuk mendapatkan hasil dalam bahasa lain? Pertanyaannya terbuka. Versi paralel dengan python , perl , awk tidak menunjukkan perbedaan yang mencolok. Ada kemungkinan bahwa guru dengan python akan mencapai hasil yang lebih baik, tetapi jangan lupa bahwa ini hanyalah tugas berkala "satu kali" yang lewat.


Memulihkan urutan foto


Setelah perjalanan dengan beberapa perangkat, Anda harus mengumpulkan semua foto dan mengaturnya sebelum diproses lebih lanjut. Salah satu opsi terbaik adalah memberi nama file berdasarkan tanggal pemotretan ( YYYY-MM-DD hh_mm_ss ), sehingga memastikan urutan foto pada panah waktu. Atribut Exif membantu menyelesaikan masalah ini dalam satu langkah.


Dan ini juga bisa dilakukan menggunakan R dalam "beberapa baris". exifr dan exifr untuk membantu.


  • membuat daftar file;
  • menarik atribut;
  • file yang disalin dengan mengganti nama mnrt. dengan atribut yang tepat.

Faktanya, tugas dikurangi menjadi yang sebelumnya, hanya atribut yang dikumpulkan bukan oleh nama file, tetapi oleh atribut exifnya, dan dalam pemrosesan hanya menyalin file dengan penggantian nama. Kerangka skrip dan logika kerja tetap tidak berubah.


Contoh kode tangan cepat:
 library(tidyverse) library(magrittr) library(stringi) library(lubridate) library(stringi) library(fs) library(glue) library(futile.logger) library(anytime) library(tictoc) library(bench) library(exifr) library(tictoc) input_path <- "S:/ " %>% fs::path_real() #       output_path <- "S:/ " %>% fs::path_real() i_fnames <- input_path %>% fs::dir_ls(recurse = TRUE, regexp = "(JPG|jpg)$") raw_df <- read_exif(i_fnames, tags = c("SourceFile", "Model", "DateTimeOriginal")) %>% #      base64, ,    mutate(tmp = sub("^base64:(.*)", "\\1", SourceFile)) %>% mutate(i_fname = purrr::map_chr(tmp, ~rawToChar(jsonlite::base64_dec(.)))) %>% mutate(tm = anytime::anytime(DateTimeOriginal)) %>% select(i_fname, DateTimeOriginal, model = Model, tm) #         clean_df <- raw_df %>% mutate(timestamp = case_when( model == 'iPhone ...' ~ tm, model == 'Nikon ...' ~ tm - lubridate::minutes(56), model == 'Samsung ...' ~ tm - lubridate::minutes(62), TRUE ~ tm) ) %>% mutate_at("i_fname", fs::path_real) %>% mutate(fname = format(timestamp, format = '%Y-%m-%d %H_%M_%S')) %>% #  ,     (""),      mutate(fname = dplyr::coalesce(fname, fs::path_ext_remove(fs::path_file(i_fname)))) %>% #  ,         group_by(fname) %>% mutate(n = n(), idx = row_number()) %>% ungroup() %>% #        mutate(fname = case_when( n > 1 ~ stri_c(fname, '_', idx), TRUE ~ fname ) ) %>% mutate(o_fname = fs::path(!!output_path, paste0(fname, ".jpg"))) #     janitor::get_dupes(clean_df, o_fname) #   tic(" ") clean_df %$% # purrr::walk2(i_fname, o_fname, ~print(glue("{.x} -> {.y}"))) purrr::walk2(i_fname, o_fname, ~fs::file_copy(.x, .y, overwrite = TRUE)) toc() #              

Mengapa exifr ? Karena itu adalah pembungkus untuk utilitas lintas-platform yang kuat, ExifTool .


Mungkin tugasnya terlihat sintetik, yang sulit diperdebatkan, karena ada banyak utilitas dan GUI yang berbeda untuk bekerja dengan Exif dan mengganti nama, tetapi ada nuansa. Tidak semua perangkat dapat mengambil zona waktu yang diubah dan menyesuaikan waktu (kamera, misalnya, seberapa sering pengguna kamera mengatur waktu yang tepat di atasnya?), Jadi selama mengganti nama Anda juga perlu menggeser perangko waktu berdasarkan sumber.


Penyelesaian


Ada banyak masalah serupa, banyak dari mereka dapat diselesaikan dengan bantuan R juga.


Publikasi sebelumnya - β€œAnak-anak, Matematika dan R” .

Source: https://habr.com/ru/post/id464849/


All Articles