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, pluginLệ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}#include "stplugin.h"là thư viện bắt buộc để truy cập vào các hàm giao tiếp với Stata.- Hàm
stata_call()là điểm vào chính của plugin — mọi tham số từ Stata đều được truyền quaargc(số lượng tham số) vàargv(mảng chuỗi tham số). SF_display()dùng để in ra dòng thông báo vào cửa sổ Stata.return(0)báo hiệu thành công.
Để 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.pluginSau khi tạo file hello.plugin, chạy lệnh trong Stata:
1. myhello
2Hello from CTruy 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, pluginCá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}SF_in1(),SF_in2()lấy ra đầu và cuối mẫu theo điều kiệnin.SF_nvars()lấy số biến đã truyền vào.SF_ifobs(i)kiểm tra xem quan sát thứicó được chọn trong mẫu hay không (dựa vào biếntouse).SF_vdata(j, i, &value)đọc giá trị của biến thứjở quan sát thứi.SF_is_missing(value)kiểm tra giá trị thiếu — rất quan trọng vìmissingtrong Stata là một số cụ thể trong kiểudouble.
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 6Tí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, pluginLệ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:
InitCmat(): phân bổ bộ nhớ cho ma trận trong C.MyAve(): tính trung bình từng biến.MyV(): tính ma trận hiệp phương sai (VCE).CopyCtoStataMatrix(): chuyển dữ liệu từ C sang Stata.SF_scal_save(): lưu số quan sát vào một scalar Stata.
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:
- Tối ưu hóa bộ nhớ: tránh sao chép dữ liệu không cần thiết.
- Dùng các thư viện C chuyên dụng (như BLAS, LAPACK) cho các phép toán ma trận.
- Xây dựng các công cụ tùy chỉnh với hiệu suất gần như bằng mã máy.
Đ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 ý:
- Dùng
SF_vdata()để đọc toàn bộ giá trị vào một mảngST_double. - Sắp xếp mảng theo thứ tự tăng dần.
- Tính trung vị: nếu số quan sát lẻ, lấy phần tử ở giữa; nếu chẵn, lấy trung bình của hai phần tử giữa.
- Lưu kết quả vào một scalar Stata và hiển thị.
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.


