messenger_logo
Liên hệ qua Messenger
SciEco

Viết một lệnh ước lượng trong Stata: Viết plugin C để tính trung bình

I
IEFPA
Ngày viết: 07/10/2026

Trong hành trình phát triển các lệnh ước lượng chuyên sâu trong Stata, việc tích hợp mã nguồn từ ngôn ngữ khác như C, C++ hay Java mở ra một thế giới mới về hiệu suất và khả năng tùy biến. Một trong những kỹ thuật mạnh mẽ nhất là viết plugin — hay còn gọi là thư viện liên kết động (DLL) — để tận dụng sức mạnh của C trong môi trường Stata.

Bài viết này là phần thứ hai trong chuỗi chuyên đề “Viết một lệnh ước lượng trong Stata”, tiếp nối bài trước về việc chuẩn bị viết plugin. Ở đây, chúng ta sẽ xây dựng một plugin C thực hiện chính xác các phép tính mà hàm mymean_work() trong tệp mymean11.ado đã làm. Mục tiêu không chỉ là minh họa cách giao tiếp giữa C và Stata, mà còn cung cấp một nền tảng vững chắc cho các tính toán hiệu suất cao trong các mô hình kinh tế lượng phức tạp.

Viết một plugin C đơn giản: “Hello, C!”

Trước khi tiến vào tính toán thực tế, hãy bắt đầu bằng một ví dụ đơn giản: một plugin C hiển thị thông điệp “Hello from C” trong cửa sổ Stata. Mã nguồn cho myhello.ado được trình bày như sau:

1*! version 1.0.0  13Feb2018
2program define myhello
3
4    version 15.1
5
6    plugin call hello
7
8end
9
10program hello, plugin

Lệnh plugin call hello thực thi plugin có tên hello, trong khi dòng program hello, plugin khai báo rằng hàm này được triển khai trong một plugin bên ngoài. Quan trọng cần lưu ý: mặc dù plugin call được gọi trước, Stata sẽ tải plugin vào trước khi thực thi dòng lệnh — do cơ chế tải toàn bộ file .ado trước khi thực hiện.

Tệp hello.c chứa mã C thực tế:

1// version 1.0.0 14Feb2018
2#include "stplugin.h"
3#include <stdio.h>
4#include <string.h>
5
6STDLL stata_call(int argc, char *argv[])
7{
8    char  msg[81];
9
10    strcpy(msg, "Hello from C\n");
11    SF_display(msg);
12    return((ST_retcode) 0);
13}

Để biên dịch thành plugin, ta dùng lệnh gcc trên macOS:

1gcc -bundle -DSYSTEM=APPLEMAC stplugin.c hello.c -o hello.plugin

Sau khi tạo file hello.plugin, chạy lệnh trong Stata:

1. myhello
2Hello from C

Truy cập dữ liệu Stata từ plugin C

Bước tiếp theo là cho phép plugin truy cập trực tiếp vào dữ liệu đang có trong Stata. Chúng ta sẽ xây dựng mylistc.ado, một lệnh liệt kê các quan sát theo biến được chỉ định — giống như list, nhưng được thực hiện hoàn toàn bằng C.

1*! version 1.0.0  13Feb2018
2program define mylistc, eclass
3
4    version 15.1
5
6    syntax varlist(numeric max=3) [if] [in]
7    marksample touse
8
9    display "Variables listed:  `varlist'"
10    plugin call mylistw `varlist' if `touse' `in'
11
12end
13
14program mylistw, plugin

Các lệnh syntax và marksample xử lý đầu vào từ người dùng: varlist chứa tên biến, if và in xác định điều kiện lọc mẫu. Plugin mylistw sẽ nhận các tham số này và xử lý dữ liệu.

Tệp mylistw.c thực hiện việc đọc dữ liệu:

1// version 1.0.0 14Feb2018
2#include "stplugin.h"
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6
7STDLL stata_call(int argc, char *argv[])
8{
9    ST_int       i, j, nObs, nVars;
10    ST_int       first, last;
11    ST_double    value;
12    ST_retcode   rc;
13    int          nchar;
14    char         line[82], strval[27], msg[81];
15
16    rc    = (ST_retcode) 0;
17    first = SF_in1();
18    last  = SF_in2();
19    nVars = SF_nvars();
20    nObs  = 0;
21
22    for(i = first; i <= last; i++) {
23        line[0] = '\0';
24        if (!SF_ifobs(i)) continue;
25        ++nObs;
26
27        for(j = 1; j <= nVars; j++) {
28            rc = SF_vdata(j, i, &value);
29            if (rc > 0) {
30                sprintf(msg, "Problem accessing Stata data\n");
31                SF_error(msg);
32                return(rc);
33            }
34            if (SF_is_missing(value)) {
35                sprintf(msg, "missing values encountered\n");
36                SF_error(msg);
37                return((ST_retcode) 416);
38            }
39
40            nchar = snprintf(strval, 25, "%f ", value);
41            if (nchar <= 0 || nchar > 25) {
42                sprintf(msg, "number is too large or badly formated\n");
43                SF_error(msg);
44                return((ST_retcode) 498);
45            }
46
47            if ((strlen(strval) + strlen(line)) <= 80) {
48                strcat(line, strval);
49            } else {
50                sprintf(msg, "More than 80 bytes in line\n");
51                SF_error(msg);
52                return((ST_retcode) 498);
53            }
54        }
55
56        strcat(line, "\n");
57        SF_display(line);
58    }
59
60    sprintf(line, "First observation was             %d\n", first);
61    SF_display(line);
62    sprintf(line, "Last observation was              %d\n", last);
63    SF_display(line);
64    sprintf(line, "Number of observations listed was %d\n", nObs);
65    SF_display(line);
66
67    return(rc);
68}

Kết quả khi chạy:

1. sysuse auto, clear
2(1978 Automobile Data)
3
4. mylistc mpg trunk rep78 if trunk < 21 in 2/10
5Variables listed:  mpg trunk rep78
617.000000 11.000000 3.000000
720.000000 16.000000 3.000000
8...
9First observation was             2
10Last observation was              10
11Number of observations listed was 6

Tính toán trung bình trong plugin C

Bây giờ, ta đi đến mục tiêu chính: viết một plugin C để ước lượng trung bình và phương sai — thay cho hàm Mata trong mymean11.ado.

Tệp mymeanc.ado:

1*! version 1.0.0  13Feb2018
2program define mymeanc, eclass
3
4    version 15.1
5
6    syntax varlist(numeric) [if] [in]
7    marksample touse
8    tempname b V N
9
10    local k : word count `varlist'
11    matrix `b' = J(1, `k', .)
12    matrix `V' = J(`k', `k', .)
13
14    plugin call mycalcs `varlist' if `touse' `in', `b' `V' `N'
15
16    matrix colnames `b'  = `varlist'
17    matrix colnames `V'  = `varlist'
18    matrix rownames `V'  = `varlist'
19    ereturn post `b' `V', esample(`touse')
20    ereturn scalar   N   = `N'
21    ereturn scalar df_r  = `N' - 1
22    ereturn display
23
24end
25
26program mycalcs, plugin

Lệnh plugin call mycalcs truyền các tên tạm thời b, V, N để plugin ghi kết quả vào các đối tượng Stata.

Tệp mycalcs.c chứa điểm vào chính:

1// version 1.0.0 14Feb2018
2#include "stplugin.h"
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include "mycalcsw.h"
7
8STDLL stata_call(int argc, char *argv[])
9{
10    ST_int       first, last, nVars, nObs;
11    ST_int       i;
12    ST_retcode   rc;
13    char         bname[130], vname[130], nname[130], msg[81];
14    ST_double    *bmat, *vmat;
15
16    bmat = NULL;
17    vmat = NULL;
18
19    nVars = SF_nvars();
20    first = SF_in1();
21    last  = SF_in2();
22
23    for(i = 0; i < 3; i++) {
24        if (strlen(argv[i]) > 129) {
25            sprintf(msg, "Argument %d is more than 129 bytes long\n", i+1);
26            SF_error(msg);
27            return((ST_retcode) 198);
28        }
29    }
30
31    strcpy(bname, argv[0]);
32    strcpy(vname, argv[1]);
33    strcpy(nname, argv[2]);
34
35    rc = InitCmat(&bmat, 1, nVars);
36    if (rc > 0) return(rc);
37
38    rc = InitCmat(&vmat, nVars, nVars);
39    if (rc > 0) {
40        free(bmat);
41        return(rc);
42    }
43
44    rc = MyAve(bmat, first, last, nVars, &nObs);
45    if (rc > 0) {
46        free(bmat);
47        free(vmat);
48        return(rc);
49    }
50
51    rc = MyV(bmat, vmat, first, last, nVars, nObs);
52    if (rc > 0) {
53        free(bmat);
54        free(vmat);
55        return(rc);
56    }
57
58    rc = CopyCtoStataMatrix(bmat, bname, 1, nVars);
59    if (rc > 0) {
60        free(bmat);
61        free(vmat);
62        return(rc);
63    }
64
65    rc = CopyCtoStataMatrix(vmat, vname, nVars, nVars);
66    if (rc > 0) {
67        free(bmat);
68        free(vmat);
69        return(rc);
70    }
71
72    rc = SF_scal_save(nname, (ST_double) nObs);
73    if (rc > 0) {
74        free(bmat);
75        free(vmat);
76        return(rc);
77    }
78
79    free(bmat);
80    free(vmat);
81    return(rc);
82}

Các chức năng chính:

Kết quả chạy:

1. mymeanc mpg trunk rep78 in 1/60
2------------------------------------------------------------------------------
3             |      Coef.   Std. Err.      t    P>|t|     [95% Conf. Interval]
4-------------+----------------------------------------------------------------
5         mpg |     20.125   .6659933    30.22   0.000     18.79032    21.45968
6       trunk |   14.42857   .5969931    24.17   0.000     13.23217    15.62497
7       rep78 |   3.160714    .118915    26.58   0.000     2.922403    3.399025
8------------------------------------------------------------------------------

Giá trị đắt giá ✨

Việc tích hợp C vào Stata không chỉ tăng tốc độ xử lý — đặc biệt với dữ liệu lớn — mà còn cho phép:

Điều này mở ra cánh cửa cho các mô hình thống kê phức tạp, phân tích dữ liệu thời gian thực, hoặc tích hợp với hệ thống bên ngoài.

Bài tập tư duy

Thử viết một plugin C để tính trung vị của một biến số, sử dụng thuật toán sắp xếp nhanh (quicksort) trong C. Gợi ý:

Bạn có thể bắt đầu từ mẫu mylistw.c, thay đổi logic xử lý và kiểm tra với dữ liệu auto.


Bài viết khác
Trong bài viết trước, chúng ta đã xây dựng một đường ống phân tích văn bản sử dụng quanteda. Quy trình đó tạo ra ma trận đặc trưng văn bản (DFM), áp dụng trọng số TF-IDF và đo lường sự tương đồng qua cosine similarity. Tuy nhiên, phương pháp này bộc lộ một hạn chế căn bản: cosine similarity tính trên các vector TF-IDF thưa chỉ phản ánh sự trùng lặp từ vựng, chứ không nắm bắt được ý nghĩa ngữ nghĩa. Xét ví dụ hai câu: "Người vay nợ không thực hiện nghĩa vụ trả nợ" và "Khách hàng không đáp ứng được cam kết trả nợ". Sau khi loại từ dừng, chúng không chia sẻ từ vựng nào, dẫn đến độ tương đồng TF-IDF gần như bằng 0. Dù vậy, bất kỳ аналитик nào cũng nhận ra chúng mang ý nghĩa như nhau. Giải pháp nằm ở vector nhúng dày đặc (dense embeddings) — biểu diễn thần kinh nơi các văn bản mang ý nghĩa gần nhau được ánh xạ đến các vị trí lân cận trong không gian vectơ. Nhờ đó, chúng ta có thể đo lường sự tương đồng ngữ nghĩa ngay cả khi không có từ chung. Bài viết này sẽ hướng dẫn xây dựng một đường ống Tạo sinh tăng cường truy xuất (RAG) trong R, sử dụng ragnar cho kho truy xuất và ellmer cho tương tác mô hình ngôn ngữ lớn (LLM), minh họa sức mạnh của nhúng dày đặc trong truy xuất và sinh câu trả lời. Toàn bộ quy trình chạy cục bộ nhờ Ollama.
Gói deltatest vừa phát hành phiên bản 0.2.0 với hai cập nhật quan trọng: phương thức tidy() cho đối tượng deltatest và sửa lỗi tính giá trị p cho kiểm định một phía. Trước khi đi vào chi tiết, hãy nhắc lại nhanh mục đích của gói này. deltatest giải quyết bài toán gì deltatest cung cấp hàm deltatest() để thực hiện kiểm định Z hai mẫu dựa trên phương pháp Delta. Gói này thiết kế cho bối cảnh phổ biến trong thử nghiệm A/B trực tuyến: ngẫu nhiên hóa ở mức người dùng nhưng chỉ số đo lường ở đơn vị tốt hơn như lượt xem trang hoặc phiên làm việc. Trong thiết lập này, các kiểm định ngây thơ (Z-test chuẩn, kiểm định chi-bình phương, kiểm định hiệu tỷ lệ) thường thấp 추정 độ không chắc chắn vì các quan sát trong cùng một người dùng không độc lập. deltatest() khắc phục vấn đề này bằng bộ ước lượng phương sai dựa trên phương pháp Delta.
SciEco
Science for Economics
Định hướng đào tạo phân tích dữ liệu, xây dựng chính sách, tối ưu hoá danh mục tài chính cá nhân và dự báo thị trường.
Liên hệ
Địa chỉ: Số 60, ngõ 41, Phố Thái Hà, Trung Liệt, Đống Đa, Hà Nội (Google Map)
Email: [email protected]
Hotline: 03.57.94.7680 (Mrs. Hà)
Mạng xã hội