sequenzo 0.1.17__cp39-cp39-win_amd64.whl → 0.1.18__cp39-cp39-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sequenzo might be problematic. Click here for more details.

Files changed (101) hide show
  1. sequenzo/__init__.py +25 -1
  2. sequenzo/big_data/clara/clara.py +1 -1
  3. sequenzo/big_data/clara/utils/get_weighted_diss.c +156 -156
  4. sequenzo/big_data/clara/utils/get_weighted_diss.cp39-win_amd64.pyd +0 -0
  5. sequenzo/clustering/clustering_c_code.cp39-win_amd64.pyd +0 -0
  6. sequenzo/clustering/hierarchical_clustering.py +202 -8
  7. sequenzo/define_sequence_data.py +34 -2
  8. sequenzo/dissimilarity_measures/c_code.cp39-win_amd64.pyd +0 -0
  9. sequenzo/dissimilarity_measures/get_substitution_cost_matrix.py +1 -1
  10. sequenzo/dissimilarity_measures/src/DHDdistance.cpp +13 -37
  11. sequenzo/dissimilarity_measures/src/LCPdistance.cpp +13 -37
  12. sequenzo/dissimilarity_measures/src/OMdistance.cpp +12 -47
  13. sequenzo/dissimilarity_measures/src/OMspellDistance.cpp +103 -67
  14. sequenzo/dissimilarity_measures/src/dp_utils.h +160 -0
  15. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/common/xsimd_common_arithmetic.hpp +41 -16
  16. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/common/xsimd_common_complex.hpp +4 -0
  17. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/common/xsimd_common_details.hpp +7 -0
  18. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/common/xsimd_common_logical.hpp +10 -0
  19. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/common/xsimd_common_math.hpp +127 -43
  20. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/common/xsimd_common_memory.hpp +30 -2
  21. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/common/xsimd_common_swizzle.hpp +174 -0
  22. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/common/xsimd_common_trigo.hpp +14 -5
  23. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_avx.hpp +111 -54
  24. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_avx2.hpp +131 -9
  25. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_avx512bw.hpp +11 -113
  26. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_avx512dq.hpp +39 -7
  27. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_avx512f.hpp +336 -30
  28. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_avx512vbmi.hpp +9 -37
  29. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_avx512vbmi2.hpp +58 -0
  30. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_common.hpp +1 -0
  31. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_common_fwd.hpp +35 -2
  32. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_constants.hpp +3 -1
  33. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_emulated.hpp +17 -0
  34. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_fma3_avx.hpp +13 -0
  35. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_fma3_sse.hpp +18 -0
  36. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_fma4.hpp +13 -0
  37. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_isa.hpp +8 -0
  38. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_neon.hpp +363 -34
  39. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_neon64.hpp +7 -0
  40. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_rvv.hpp +13 -0
  41. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_scalar.hpp +41 -4
  42. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_sse2.hpp +252 -16
  43. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_sse3.hpp +9 -0
  44. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_ssse3.hpp +12 -1
  45. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_sve.hpp +7 -0
  46. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_vsx.hpp +892 -0
  47. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/arch/xsimd_wasm.hpp +78 -1
  48. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/config/xsimd_arch.hpp +3 -1
  49. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/config/xsimd_config.hpp +13 -2
  50. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/config/xsimd_cpuid.hpp +5 -0
  51. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/config/xsimd_inline.hpp +5 -1
  52. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/types/xsimd_all_registers.hpp +2 -0
  53. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/types/xsimd_api.hpp +64 -1
  54. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/types/xsimd_batch.hpp +36 -0
  55. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/types/xsimd_rvv_register.hpp +40 -31
  56. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/types/xsimd_traits.hpp +8 -0
  57. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/types/xsimd_vsx_register.hpp +77 -0
  58. sequenzo/dissimilarity_measures/src/xsimd/include/xsimd/xsimd.hpp +6 -0
  59. sequenzo/dissimilarity_measures/src/xsimd/test/test_basic_math.cpp +6 -0
  60. sequenzo/dissimilarity_measures/src/xsimd/test/test_batch.cpp +54 -2
  61. sequenzo/dissimilarity_measures/src/xsimd/test/test_batch_bool.cpp +8 -0
  62. sequenzo/dissimilarity_measures/src/xsimd/test/test_batch_cast.cpp +11 -4
  63. sequenzo/dissimilarity_measures/src/xsimd/test/test_batch_complex.cpp +18 -0
  64. sequenzo/dissimilarity_measures/src/xsimd/test/test_batch_int.cpp +8 -14
  65. sequenzo/dissimilarity_measures/src/xsimd/test/test_batch_manip.cpp +216 -173
  66. sequenzo/dissimilarity_measures/src/xsimd/test/test_load_store.cpp +6 -0
  67. sequenzo/dissimilarity_measures/src/xsimd/test/test_memory.cpp +1 -1
  68. sequenzo/dissimilarity_measures/src/xsimd/test/test_power.cpp +7 -4
  69. sequenzo/dissimilarity_measures/src/xsimd/test/test_select.cpp +6 -2
  70. sequenzo/dissimilarity_measures/src/xsimd/test/test_shuffle.cpp +32 -18
  71. sequenzo/dissimilarity_measures/src/xsimd/test/test_utils.hpp +21 -24
  72. sequenzo/dissimilarity_measures/src/xsimd/test/test_xsimd_api.cpp +69 -9
  73. sequenzo/dissimilarity_measures/utils/get_sm_trate_substitution_cost_matrix.c +156 -156
  74. sequenzo/dissimilarity_measures/utils/get_sm_trate_substitution_cost_matrix.cp39-win_amd64.pyd +0 -0
  75. sequenzo/dissimilarity_measures/utils/seqconc.c +156 -156
  76. sequenzo/dissimilarity_measures/utils/seqconc.cp39-win_amd64.pyd +0 -0
  77. sequenzo/dissimilarity_measures/utils/seqdss.c +156 -156
  78. sequenzo/dissimilarity_measures/utils/seqdss.cp39-win_amd64.pyd +0 -0
  79. sequenzo/dissimilarity_measures/utils/seqdur.c +156 -156
  80. sequenzo/dissimilarity_measures/utils/seqdur.cp39-win_amd64.pyd +0 -0
  81. sequenzo/dissimilarity_measures/utils/seqlength.c +156 -156
  82. sequenzo/dissimilarity_measures/utils/seqlength.cp39-win_amd64.pyd +0 -0
  83. sequenzo/sequence_characteristics/__init__.py +4 -0
  84. sequenzo/sequence_characteristics/complexity_index.py +17 -57
  85. sequenzo/sequence_characteristics/overall_cross_sectional_entropy.py +177 -111
  86. sequenzo/sequence_characteristics/plot_characteristics.py +30 -11
  87. sequenzo/sequence_characteristics/simple_characteristics.py +1 -0
  88. sequenzo/sequence_characteristics/state_frequencies_and_entropy_per_sequence.py +9 -3
  89. sequenzo/sequence_characteristics/turbulence.py +47 -67
  90. sequenzo/sequence_characteristics/variance_of_spell_durations.py +19 -9
  91. sequenzo/sequence_characteristics/within_sequence_entropy.py +5 -58
  92. sequenzo/visualization/plot_sequence_index.py +58 -35
  93. sequenzo/visualization/plot_state_distribution.py +57 -36
  94. sequenzo/with_event_history_analysis/__init__.py +35 -0
  95. sequenzo/with_event_history_analysis/sequence_analysis_multi_state_model.py +850 -0
  96. sequenzo/with_event_history_analysis/sequence_history_analysis.py +283 -0
  97. {sequenzo-0.1.17.dist-info → sequenzo-0.1.18.dist-info}/METADATA +7 -6
  98. {sequenzo-0.1.17.dist-info → sequenzo-0.1.18.dist-info}/RECORD +101 -94
  99. {sequenzo-0.1.17.dist-info → sequenzo-0.1.18.dist-info}/WHEEL +0 -0
  100. {sequenzo-0.1.17.dist-info → sequenzo-0.1.18.dist-info}/licenses/LICENSE +0 -0
  101. {sequenzo-0.1.17.dist-info → sequenzo-0.1.18.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,283 @@
1
+ """
2
+ @Author : Yuqi Liang 梁彧祺
3
+ @File : sequence_history_analysis.py
4
+ @Time : 30/09/2025 21:08
5
+ @Desc : Sequence History Analysis - Convert person-level sequence data to person-period format
6
+ """
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+
11
+
12
+ def person_level_to_person_period(data, id_col="id", period_col="time", event_col="event"):
13
+ """
14
+ Convert person-level data to person-period format.
15
+
16
+ This function expands each person's single row into multiple rows,
17
+ one for each time period they are observed.
18
+
19
+ Parameters
20
+ ----------
21
+ data : pandas.DataFrame
22
+ Input data with one row per person
23
+ id_col : str, optional
24
+ Name of the ID column (default: "id")
25
+ period_col : str, optional
26
+ Name of the time period column (default: "time")
27
+ event_col : str, optional
28
+ Name of the event indicator column (default: "event")
29
+
30
+ Returns
31
+ -------
32
+ pandas.DataFrame
33
+ Expanded data with one row per person-period
34
+
35
+ Examples
36
+ --------
37
+ >>> data = pd.DataFrame({'id': [1, 2], 'time': [3, 2], 'event': [True, False]})
38
+ >>> person_level_to_person_period(data)
39
+ id time event
40
+ 0 1 1 False
41
+ 1 1 2 False
42
+ 2 1 3 True
43
+ 3 2 1 False
44
+ 4 2 2 False
45
+ """
46
+ # Check for missing values in critical columns
47
+ if data[[id_col, period_col, event_col]].isna().any().any():
48
+ raise ValueError("Cannot handle missing data in the time or event variables")
49
+
50
+ # Create an index that repeats each row based on the time value
51
+ # For example, if time=3, that row will be repeated 3 times
52
+ index = np.repeat(np.arange(len(data)), data[period_col].values)
53
+
54
+ # Find the cumulative sum to identify which rows should have the event
55
+ idmax = np.cumsum(data[period_col].values) - 1
56
+
57
+ # Expand the data by repeating rows
58
+ dat = data.iloc[index].copy()
59
+ dat.reset_index(drop=True, inplace=True)
60
+
61
+ # Create sequential time periods for each ID (1, 2, 3, ...)
62
+ dat[period_col] = dat.groupby(id_col).cumcount() + 1
63
+
64
+ # Set all events to False initially
65
+ dat[event_col] = False
66
+
67
+ # Set events to True only at the final period for each person
68
+ # Convert to bool to avoid dtype incompatibility warning
69
+ dat.loc[idmax, event_col] = data[event_col].values.astype(bool)
70
+
71
+ return dat
72
+
73
+
74
+ def _extract_sequence_dataframe(seqdata):
75
+ """
76
+ Extract sequence DataFrame from various input types.
77
+
78
+ Parameters
79
+ ----------
80
+ seqdata : SequenceData, pandas.DataFrame, or numpy.ndarray
81
+ Input sequence data
82
+
83
+ Returns
84
+ -------
85
+ pandas.DataFrame
86
+ Sequence data as a DataFrame
87
+ """
88
+ # Check if input is a SequenceData object
89
+ if hasattr(seqdata, 'seqdata'):
90
+ # This is a SequenceData object
91
+ return seqdata.seqdata.copy()
92
+ elif isinstance(seqdata, pd.DataFrame):
93
+ return seqdata.copy()
94
+ else:
95
+ # Assume it's array-like
96
+ return pd.DataFrame(seqdata)
97
+
98
+
99
+ def seqsha(seqdata, time, event, include_present=False, align_end=False, covar=None):
100
+ """
101
+ Sequence History Analysis: Create person-period format with sequence history.
102
+
103
+ This function converts sequence data into a person-period format where each
104
+ row represents a time point for a person, with columns showing their sequence
105
+ history up to that point.
106
+
107
+ Parameters
108
+ ----------
109
+ seqdata : SequenceData, pandas.DataFrame, or numpy.ndarray
110
+ Sequence data where each row is a person and each column is a time point.
111
+ Can be a SequenceData object, DataFrame, or array.
112
+ time : array-like
113
+ Duration or time until event for each person. Length should equal the
114
+ number of sequences. Each value indicates how many time periods that
115
+ person is observed. For example, if all persons are observed for the
116
+ full sequence length, use: np.full(n_persons, sequence_length)
117
+ event : array-like
118
+ Event indicator for each person (True/False or 1/0). Length should
119
+ equal the number of sequences.
120
+ include_present : bool, optional
121
+ If True, include the current time point in the history (default: False)
122
+ If False, only include past time points (recommended for most analyses)
123
+ align_end : bool, optional
124
+ If True, align sequences from the end (right-aligned) (default: False)
125
+ If False, align sequences from the start (left-aligned)
126
+ covar : pandas.DataFrame or numpy.ndarray, optional
127
+ Additional covariates to merge with the output (default: None)
128
+ Should have the same number of rows as seqdata
129
+
130
+ Returns
131
+ -------
132
+ pandas.DataFrame
133
+ Person-period data with the following columns:
134
+ - id: Person identifier
135
+ - time: Time period within person
136
+ - event: Event indicator (True only at the final period for each person)
137
+ - Sequence history columns (varies based on align_end parameter)
138
+ - Additional covariate columns (if covar is provided)
139
+
140
+ Raises
141
+ ------
142
+ ValueError
143
+ If maximum time exceeds the length of the longest sequence
144
+
145
+ Examples
146
+ --------
147
+ Example 1: Basic usage with DataFrame
148
+ >>> import pandas as pd
149
+ >>> import numpy as np
150
+ >>> seqdata = pd.DataFrame([[1, 2, 3, 4], [1, 1, 2, 2]])
151
+ >>> time = np.array([3, 2])
152
+ >>> event = np.array([True, False])
153
+ >>> result = seqsha(seqdata, time, event)
154
+
155
+ Example 2: Usage with SequenceData object (recommended)
156
+ >>> from sequenzo import SequenceData, load_dataset
157
+ >>> df = load_dataset('pairfam_family')
158
+ >>> time_cols = [str(i) for i in range(1, 265)]
159
+ >>> seq_data = SequenceData(df, time=time_cols, id_col='id',
160
+ ... states=list(range(1, 10)))
161
+ >>> # All persons observed for 264 months
162
+ >>> time = np.full(len(df), 264)
163
+ >>> event = df['highschool'].values
164
+ >>> result = seqsha(seq_data, time, event)
165
+
166
+ Example 3: With covariates
167
+ >>> covar = df[['sex', 'yeduc', 'east']]
168
+ >>> result = seqsha(seq_data, time, event, covar=covar)
169
+
170
+ Example 4: Right-aligned sequences
171
+ >>> result = seqsha(seq_data, time, event, align_end=True)
172
+
173
+ Notes
174
+ -----
175
+ - The time parameter represents observation duration, not calendar time
176
+ - When include_present=False (default), only past states are included
177
+ - Use align_end=True when analyzing sequences leading up to an event
178
+ - Missing values in the original sequence are converted to "NA_orig"
179
+ """
180
+ # Extract sequence DataFrame from input (handles SequenceData, DataFrame, or array)
181
+ seq_df = _extract_sequence_dataframe(seqdata)
182
+
183
+ # Convert time and event to numpy arrays for consistency
184
+ time_array = np.asarray(time)
185
+ event_array = np.asarray(event)
186
+
187
+ # Check that dimensions match
188
+ n_sequences = len(seq_df)
189
+ if len(time_array) != n_sequences:
190
+ raise ValueError(
191
+ f"Length of 'time' ({len(time_array)}) must match number of sequences ({n_sequences})"
192
+ )
193
+ if len(event_array) != n_sequences:
194
+ raise ValueError(
195
+ f"Length of 'event' ({len(event_array)}) must match number of sequences ({n_sequences})"
196
+ )
197
+
198
+ # Create base time data: one row per person with their time and event
199
+ basetime = pd.DataFrame({
200
+ 'id': np.arange(1, n_sequences + 1),
201
+ 'time': time_array,
202
+ 'event': event_array
203
+ })
204
+
205
+ # Convert to person-period format (expand rows)
206
+ persper = person_level_to_person_period(basetime, "id", "time", "event")
207
+
208
+ # Convert sequence data to matrix and handle missing values
209
+ sdata = seq_df.values.astype(str)
210
+ sdata[pd.isna(seq_df.values)] = "NA_orig"
211
+
212
+ # Get the time periods for each row in person-period data
213
+ age = persper['time'].values
214
+ ma = int(np.max(age))
215
+
216
+ # Check if time values are valid
217
+ if ma > seq_df.shape[1]:
218
+ raise ValueError("Maximum time of event occurrence is higher than the longest sequence!")
219
+
220
+ # Create empty matrix to store past sequence states
221
+ past = np.full((len(persper), seq_df.shape[1]), np.nan, dtype=object)
222
+
223
+ if align_end:
224
+ # Right-align the sequences (align from the end)
225
+ start = 1 if include_present else 2
226
+
227
+ for aa in range(start, ma + 1):
228
+ # Find rows where time equals aa
229
+ cond = age == aa
230
+ # Get the person IDs for these rows
231
+ ids_a = persper.loc[cond, 'id'].values - 1 # Subtract 1 for 0-based indexing
232
+
233
+ if include_present:
234
+ # Include current time point: fill from (ncol-aa) to end
235
+ past[cond, (seq_df.shape[1] - aa):seq_df.shape[1]] = sdata[ids_a, 0:aa]
236
+ else:
237
+ # Exclude current time point: fill from (ncol-aa+1) to end
238
+ past[cond, (seq_df.shape[1] - aa + 1):seq_df.shape[1]] = sdata[ids_a, 0:(aa - 1)]
239
+
240
+ # Create column names counting backwards
241
+ col_names = [f"Tm{i}" for i in range(seq_df.shape[1], 0, -1)]
242
+ else:
243
+ # Left-align the sequences (align from the start)
244
+ for aa in range(1, ma + 1):
245
+ if include_present:
246
+ # Include present: use time > aa
247
+ cond = age > aa
248
+ else:
249
+ # Exclude present: use time >= aa
250
+ cond = age >= aa
251
+
252
+ # Get the person IDs for these rows
253
+ ids_a = persper.loc[cond, 'id'].values - 1 # Subtract 1 for 0-based indexing
254
+
255
+ # Fill in the sequence state at position aa-1 (0-based)
256
+ past[cond, aa - 1] = sdata[ids_a, aa - 1]
257
+
258
+ # Use original column names or create default ones
259
+ if seq_df.columns is not None and len(seq_df.columns) > 0:
260
+ col_names = [str(col) for col in seq_df.columns[:ma]]
261
+ # Pad with additional column names if needed
262
+ col_names += [f"col_{i}" for i in range(ma, seq_df.shape[1])]
263
+ else:
264
+ col_names = [f"col_{i}" for i in range(seq_df.shape[1])]
265
+
266
+ # Convert past matrix to DataFrame
267
+ past_df = pd.DataFrame(past, columns=col_names)
268
+
269
+ # Combine person-period data with sequence history
270
+ alldata = pd.concat([persper.reset_index(drop=True), past_df], axis=1)
271
+
272
+ # Add covariates if provided
273
+ if covar is not None:
274
+ # Merge covariates based on the ID (subtract 1 for 0-based indexing)
275
+ if isinstance(covar, pd.DataFrame):
276
+ covar_subset = covar.iloc[alldata['id'].values - 1].reset_index(drop=True)
277
+ alldata = pd.concat([alldata, covar_subset], axis=1)
278
+ else:
279
+ covar_array = np.array(covar)
280
+ covar_subset = covar_array[alldata['id'].values - 1]
281
+ alldata = pd.concat([alldata, pd.DataFrame(covar_subset)], axis=1)
282
+
283
+ return alldata
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sequenzo
3
- Version: 0.1.17
3
+ Version: 0.1.18
4
4
  Summary: A fast, scalable and intuitive Python package for social sequence analysis.
5
5
  Author-email: Yuqi Liang <yuqi.liang.1900@gmail.com>, Xinyi Li <1836724126@qq.com>, Jan Heinrich Ernst Meyerhoff-Liang <jan.meyerhoff1@gmail.com>
6
6
  License: BSD 3-Clause License
@@ -58,6 +58,7 @@ Requires-Dist: joblib>=1.0.1
58
58
  Requires-Dist: docutils>=0.17
59
59
  Requires-Dist: tqdm<5.0.0,>=4.62.3
60
60
  Requires-Dist: missingno<0.6.0,>=0.5.2
61
+ Requires-Dist: rpy2
61
62
  Provides-Extra: dev
62
63
  Requires-Dist: pytest>=6.2.5; extra == "dev"
63
64
  Requires-Dist: flake8>=3.9.2; extra == "dev"
@@ -123,12 +124,12 @@ Perfect for research, policy, and business, enabling seamless analysis of catego
123
124
 
124
125
  Sequenzo provides pre-built Python wheels for maximum compatibility — no need to compile from source.
125
126
 
126
- | Platform | Architecture | Python Versions | Status |
127
+ | Platform | Architecture | Python Versions | Status |
127
128
  |------------------|-------------------------------|-----------------------|-------------------|
128
- | **macOS** | `universal2` (Intel + Apple Silicon) | 3.9, 3.11 | ✅ Pre-built wheel |
129
- | **Windows** | `AMD64` (64-bit) | 3.9, 3.10, 3.11 | ✅ Pre-built wheel |
130
- | **Linux (glibc)**| `x86_64` (standard Linux) | 3.9, 3.10, 3.11 | ✅ Pre-built wheel |
131
- | **Linux (musl)** | `x86_64` (Alpine Linux) | 3.9, 3.10, 3.11 | ✅ Pre-built wheel |
129
+ | **macOS** | `universal2` (Intel + Apple Silicon) | 3.9, 3.10, 3.11, 3.12 | ✅ Pre-built wheel |
130
+ | **Windows** | `AMD64` (64-bit) | 3.9, 3.10, 3.11, 3.12 | ✅ Pre-built wheel |
131
+ | **Linux (glibc)**| `x86_64` (standard Linux) | 3.9, 3.10, 3.11, 3.12 | ✅ Pre-built wheel |
132
+ | **Linux (musl)** | `x86_64` (Alpine Linux) | 3.9, 3.10, 3.11, 3.12 | ✅ Pre-built wheel |
132
133
 
133
134
 
134
135
  What do these terms mean?