Về quy ước đặt tên dữ liệu

Danh pháp quy ước để đặt tên cho dữ liệu dạng bảng, tăng tốc độ hiểu biết và sử dụng dữ liệu
data quality
name convention
controlled vocabulary
metadata
Author
Published

June 5, 2024

Modified

July 3, 2024

Note

Các bạn cũng có thể xem thêm slide về dbtplyr của Emily trong posit::conf ở đây.


Đây là keynote từ article Column Names as Contracts của Emily Riederer.

Các sản phẩm phần mềm luôn tuân theo một quy ước nhất định (về hình ảnh, ký tự) nhằm để người dùng có thể ngay lập tức nắm bắt được cách thức sử dụng, giao tiếp với nó. Ví dụ, nút Home thường sẽ đưa người dùng về trang chủ của một mobile app, nút “blog” trên blog của tôi sẽ đưa các bạn tiếp cận danh mục bài viết, etc. Ở layer phía sau, các API được BE viết cần tuân thủ documented inputs & outputs, để các BE khác hay FE có thể sử dụng.

Tuy nhiên các bảng dữ liệu lại nằm ở một vùng màu xám không rõ ràng. Data service (từ producer i.e DE -> user i.e DA, DS) đủ tĩnh để không được xem là normal service, nhưng cũng đủ thô để người ta ít chú ý đến nó về mặt giao diện. DE cứ thể lấy hết mọi dữ liệu trong hệ thống, DA nhìn dữ liệu và ngay lập tức giả định được nội dung của (cột) dữ liệu đó.

Producers wonder why consumers aren’t satisfied, and consumers wonder why the data is never “right”.

Cũng có một cách được nhiều tổ chức sử dụng như là Metadata managament, như:

Tuy nhiên giải pháp này là documentation oriented, producer và user đều phải tuân thủ nghiêm ngặt. Nó không trực diện khi DE đặt tên cột, hay DA đọc dữ liệu.

Ý tưởng của Emily là chuẩn hóa các quy ước đặt tên dữ liệu bằng Controlled vocabulary. Minh họa bằng pointblank, collapsibleTree, và dplyr.

Controlled Vocabulary

CV là phương pháp sử dụng một tập hợp các từ, cụm từ, từ viết tắt để ký hiệu thông tin. Quy ước này cần được định nghĩa đề phù hợp cho tính chất bộ dữ liệu, và cũng đủ đơn giản để người tạo dữ liệu, người sử dụng dữ liệu sử dụng.

Ví dụ, 1 ứng dụng booking.

Level 1: Kiểu dữ liệu, kiểu đo lường

  • ID: chỉ đối tượng, dạng numberic, tối ưu cho việc lưu trữ và thường làm khóa chính các bảng’
  • IND: giá trị boolean 0/1, tôi thì thường sử dụng các từ như IS, FLAG;
  • N: chỉ các dạng thông tin có thể đếm (count) được;
  • AMT: chỉ các dạng thông tin có thể tổng, thường đề cập đến currency;
  • VAL: cũng là số nhưng không thể tổng được (không có ý nghĩa), ví dụ như kinh độ, vĩ độ;
  • DT: ngày;
  • TM: giờ hoặc ngày giờ;
  • CAT: category

Cũng tùy vào dữ liệu, có thể thêm các cụm từ nhất định đề đề cập đến thông tin dữ liệu đó mang lại, ví dụ ADD cho address.

Level 2: Đối tượng, chủ đề

Mối quan tâm của người dùng dữ liệu cũng giống như nhà quản trị, xung quanh các đối tượng của business: USER, TRANSACTION, etc.

Đối với ứng dụng đặt xe, các chủ thể có thể cần quan tâm là TRIP, TRAVELER, ACCOMODATION, TRANSPORTATION, etc.

Level 3-n: Chi tiết

Hai tầng trên về cơ bản đã cho phép chúng ta mô tả được cột thông tin, chúng đều thể hiện được thuộc tính của chủ thể. Ví dụ:

  • AMT_TRIP: giá trị deal của Trip;
  • ADD_DESTINATION: Điểm đến của trip.

Tuy nhiên tùy vào từng use case, chúng ta hoàn toàn có thể mở rộng được về cả hai phía. Ví dụ:

  • Về phía trước: khi làm việc với nhiều layer dữ liệu, ta có thể dùng RAW, STAGING, DW, DL, DM, etc
  • Về phía sau: DAY hoặc MONTH cho N_DURATION, VND hoặc USD cho currency AMT.

Miễn là có một quy chuẩn, và nó đủ đơn giản để sử dụng.

Kết hợp chúng lại

Tất nhiên ràng buộc việc đặt tên như vậy không chỉ để trang trí, nó sẽ giúp chúng ta tự động hóa quy trình kiểm soát chất lượng dữ liệu, .

Tôi sử dụng bộ dữ liệu này để thực hành (cột dữ liệu đã được đổi tên).

head(trip_data)
# A tibble: 6 × 13
  ID_TRIP CAT_DESTINATION_CITY DT_START  DT_END N_DURATION_DAY CAT_TRAVELER_NAME
    <dbl> <chr>                <chr>     <chr>           <dbl> <chr>            
1       1 London, UK           5/1/2023  5/8/2…              7 John Smith       
2       2 Phuket, Thailand     6/15/2023 6/20/…              5 Jane Doe         
3       3 Bali, Indonesia      7/1/2023  7/8/2…              7 David Lee        
4       4 New York, USA        8/15/2023 8/29/…             14 Sarah Johnson    
5       5 Tokyo, Japan         9/10/2023 9/17/…              7 Kim Nguyen       
6       6 Paris, France        10/5/2023 10/10…              5 Michael Brown    
# ℹ 7 more variables: VAL_AGE_TRAVELER <dbl>, IND_GENDER_TRAVELER <chr>,
#   CAT_TRAVELER_NATIONALITY <chr>, CAT_ACCOMMODATION_TYPE <chr>,
#   AMT_ACCOMMODATION_COST <chr>, CAT_TRANSPORTATION_TYPE <chr>,
#   AMT_TRANSPORTATION_COST <chr>

Data Validation

Sử dụng pointblank của R để kiếm tra một số tính chất của dữ liệu:

library(pointblank)

agent <-
  trip_data  %>% 
  create_agent(actions = action_levels(stop_at = 0.001)) %>% 
  col_vals_gte(starts_with("N"), 0) %>% # check if cols start with N is GTE 0 or not
  col_vals_gte(starts_with("VAL"), 0) %>% # check if cols start with VAL is GTE 0 or not
  col_vals_not_null(starts_with("IND")) %>% # check if cols start with IND contain Null or not
  col_vals_in_set(starts_with("IND"), c("Male","Female")) %>% # validate the value of only column started with IND - Gender
  col_is_date(starts_with("DT")) %>% # check validation of date columns
  interrogate()

Note: To type pipe operator in VS Code, use this in the keybindings.json file:

{
  "key": "Ctrl+Shift+m",             
  "command": "type", 
  "args": { "text": " %>% " },
  "when": "editorTextFocus 
            && !editorHasSelection 
            && editorLangId 
            == 'quarto'" 
}

Then you can get the pipe when type Ctrl + Shift + M.

agent
Pointblank Validation
[2024-07-04|09:52:39]

tibbleWARN STOP 0.00 NOTIFY
STEP COLUMNS VALUES TBL EVAL UNITS PASS FAIL W S N EXT

1
col_vals_gte
 col_vals_gte()

N_DURATION_DAY

0

139 137
0.98561
2
0.01439

2
col_vals_gte
 col_vals_gte()

VAL_AGE_TRAVELER

0

139 137
0.98561
2
0.01439

3
col_vals_not_null
 col_vals_not_null()

IND_GENDER_TRAVELER

139 137
0.98561
2
0.01439

4
col_vals_in_set
 col_vals_in_set()

IND_GENDER_TRAVELER

Male, Female

139 137
0.98561
2
0.01439

5
col_is_date
 col_is_date()

DT_START

1 0
0
1
1

6
col_is_date
 col_is_date()

DT_END

1 0
0
1
1
2024-07-04 09:52:39 +07 < 1 s 2024-07-04 09:52:39 +07

Cool! Không chỉ kiểm tra được tính đúng đắn của dữ liệu, cũng có thể xác định được biến nào không tuân theo quy tắc đặt tên.

Data Discoverability

cols_data <- names(trip_data)
cols_data_split <- strsplit(cols_data,"_")
cols_components <- data.frame(
  variable = cols_data,
  level1 = vapply(cols_data_split, FUN = function(x) x[1], FUN.VALUE = character(1)),
  level2 = vapply(cols_data_split, FUN = function(x) x[2], FUN.VALUE = character(1))
)
head(cols_components)
              variable level1      level2
1              ID_TRIP     ID        TRIP
2 CAT_DESTINATION_CITY    CAT DESTINATION
3             DT_START     DT       START
4               DT_END     DT         END
5       N_DURATION_DAY      N    DURATION
6    CAT_TRAVELER_NAME    CAT    TRAVELER

Đây là một cách để xây dựng metadata, chúng ta có thể touch một cách interactive với:

library(DT)
datatable(cols_components,  filter = list(position = 'top', clear = FALSE))

Cũng có thể visualize thành một tree như bên dưới:

library(collapsibleTree)
collapsibleTree(cols_components, 
                hierarchy = paste0("level", 1:2),
                nodeSize = "leafCount"
                )

Đây là cách xem từ kiểu dữ liệu -> chủ thể, chúng ta cũng có thể làm ngược lại:

collapsibleTree(cols_components, 
                hierarchy = paste0("level", c(2,1)),
                nodeSize = "leafCount"
                )

Bộ dữ liệu mình sử dụng khá nghèo nàn và tập trung về một chủ thể duy nhất là TRIP, nên khả năng minh họa không được phong phú 😂. Ngoài ra, việc đặt tên như thế cũng giúp ta dễ dàng gọi biến ở IDE.

Data Wrangling

Tương tự, với việc sử dụng một bộ từ điển có hệ thống, lớp lang cũng support chúng ta trong việc biến đổi dữ liệu. Ví dụ chúng ta có thể tận dụng “select helpers” trong gói dplyr:

library(dplyr)
trip_data <- as.data.frame(trip_data)
trip_data %>%
  filter(if_any(everything(), ~ !is.na(.))) %>% 
  group_by(CAT_TRAVELER_NATIONALITY) %>%
  summarize(
    across(starts_with("DT_"), c(min,max))
  )
# A tibble: 42 × 5
   CAT_TRAVELER_NATIONALITY DT_START_1 DT_START_2 DT_END_1   DT_END_2 
   <chr>                    <chr>      <chr>      <chr>      <chr>    
 1 American                 1/1/2023   9/15/2022  1/1/2023   9/22/2022
 2 Australian               11/20/2023 9/5/2022   11/30/2023 9/14/2022
 3 Brazil                   4/3/2024   4/3/2024   4/10/2024  4/10/2024
 4 Brazilian                1/15/2024  8/1/2023   1/12/2024  8/10/2023
 5 British                  1/1/2025   8/20/2022  1/15/2023  9/2/2022 
 6 Cambodia                 9/10/2022  9/10/2022  9/15/2022  9/15/2022
 7 Canada                   7/1/2022   9/1/2022   7/10/2022  9/10/2022
 8 Canadian                 1/2/2023   8/15/2023  1/9/2023   8/22/2023
 9 China                    5/8/2023   5/8/2023   5/14/2023  5/14/2023
10 Chinese                  11/11/2022 9/1/2023   11/21/2022 9/8/2023 
# ℹ 32 more rows

Other languages

Generating SQL

Sử dụng R để generate SQL query

library(dbplyr)

Attaching package: 'dbplyr'
The following objects are masked from 'package:dplyr':

    ident, sql
library(RSQLite)
df_mem <- memdb_frame(trip_data, .name = "example_table")

df_mem %>%
  group_by(CAT_TRAVELER_NATIONALITY) %>%
  summarize_at(vars(starts_with("N_")), mean, na.rm = TRUE) %>%
  show_query()
<SQL>
SELECT `CAT_TRAVELER_NATIONALITY`, AVG(`N_DURATION_DAY`) AS `N_DURATION_DAY`
FROM `example_table`
GROUP BY `CAT_TRAVELER_NATIONALITY`

R - base & data.table

sử dụng base::grep để tìm tất cả các columns bắt đầu bằng AMT_

cols_n <- grep("^AMT_", names(trip_data), value = TRUE)
print(cols_n)
[1] "AMT_ACCOMMODATION_COST"  "AMT_TRANSPORTATION_COST"

sử dụng vector để lưu các cột chúng ta muốn group by:

cols_grp <- c("CAT_TRAVELER_NATIONALITY")

chúng ta có thể dùng vector này trong stats::aggregate

hoặc trong data.table

python pandas

tương tự trong python, sử dụng list comprehension:

```{python}

import pandas as pd
cols_n   = [vbl for vbl in trip_data.columns if vbl[0:2] == 'AMT_']
cols_grp = ["CAT_TRAVELER_NATIONALITY"]
trip_data.groupby(cols_grp)[cols_n].sum()
```

Updates by Emily

Concept Map

%%{init: {'theme':'dark'}}%%
flowchart LR
  A(dataframe)-- has --> B(columns)
  A(dataframe)-- has --> C(names)
  B -- have --> D(data types)
  B -- have --> E(units)
  B -- have --> F(meaning)
  C -- should encode --> D
  C -- should encode --> E
  C -- should encode --> F
  C -- can support --> G(validation)
  C -- explained in --> H(documentation)
  G -- should check consistency of --> C
  G -- should check consistency of --> H
  H --> I(data dictionary)
  H --> K(ERD)

New Package (Dec 2020)

convo là một thư viện released bởi Emily phục vụ cho việc quản lý controlled vocabulary:

library(convo)
vocab.yaml
level1:
  ID:
    desc: Unique identifier
    valid:
      - col_vals_not_null()
      - col_is_numeric()
      - col_vals_between(1000, 99999)
  IND:
    desc: Binary indicator
    valid:
      - col_is_numeric()
      - col_vals_in_set(c(0,1))
    rename:
      - when: SUM
        then: 'N'
      - when: AVG
        then: P
  AMT:
    desc: Non-negative, summable quantity
    valid:
      - col_is_numeric()
      - col_vals_gte(0)
  VAL:
    desc: Value
    valid:
      - col_is_numeric()
    rename:
      - when: AVG
        then: VALAV

New Package (April 2021)

dbtplyr là một package port tính năng “select helpers” của dplyr sang dbt.