standpoint 0.2.0__tar.gz

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.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Warith Harchaoui.
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,309 @@
1
+ Metadata-Version: 2.4
2
+ Name: standpoint
3
+ Version: 0.2.0
4
+ Summary: Know where each option stands: a labelled 2D positioning map from any comparison table.
5
+ Author: Warith Harchaoui
6
+ License: BSD 3-Clause License
7
+
8
+ Copyright (c) 2026, Warith Harchaoui.
9
+ All rights reserved.
10
+
11
+ Redistribution and use in source and binary forms, with or without
12
+ modification, are permitted provided that the following conditions are met:
13
+
14
+ 1. Redistributions of source code must retain the above copyright notice, this
15
+ list of conditions and the following disclaimer.
16
+
17
+ 2. Redistributions in binary form must reproduce the above copyright notice,
18
+ this list of conditions and the following disclaimer in the documentation
19
+ and/or other materials provided with the distribution.
20
+
21
+ 3. Neither the name of the copyright holder nor the names of its
22
+ contributors may be used to endorse or promote products derived from
23
+ this software without specific prior written permission.
24
+
25
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
29
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
33
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+
36
+ Project-URL: Homepage, https://github.com/warith-harchaoui/standingpoint
37
+ Project-URL: Author, https://www.linkedin.com/in/warith-harchaoui
38
+ Keywords: pca,positioning map,perceptual map,quadrant,vega-lite
39
+ Classifier: License :: OSI Approved :: BSD License
40
+ Classifier: Programming Language :: Python :: 3
41
+ Classifier: Programming Language :: Python :: 3.10
42
+ Classifier: Programming Language :: Python :: 3.11
43
+ Classifier: Programming Language :: Python :: 3.12
44
+ Classifier: Programming Language :: Python :: 3.13
45
+ Classifier: Topic :: Scientific/Engineering :: Visualization
46
+ Requires-Python: >=3.10
47
+ Description-Content-Type: text/markdown
48
+ License-File: LICENSE
49
+ Requires-Dist: numpy>=1.24
50
+ Requires-Dist: pandas>=2.0
51
+ Requires-Dist: scikit-learn>=1.3
52
+ Requires-Dist: vl-convert-python>=1.0
53
+ Requires-Dist: PyYAML>=6.0
54
+ Requires-Dist: langdetect>=1.0.9
55
+ Requires-Dist: ollama>=0.3
56
+ Requires-Dist: click>=8.1
57
+ Provides-Extra: dev
58
+ Requires-Dist: pytest>=7.0; extra == "dev"
59
+ Requires-Dist: ruff>=0.5; extra == "dev"
60
+ Provides-Extra: eval
61
+ Requires-Dist: deepeval>=1.0; extra == "eval"
62
+ Dynamic: license-file
63
+
64
+ # Standpoint
65
+
66
+ [๐Ÿ‡ซ๐Ÿ‡ท](https://github.com/warith-harchaoui/standingpoint/blob/main/LISEZMOI.md) ยท [๐Ÿ‡ฌ๐Ÿ‡ง](https://github.com/warith-harchaoui/standingpoint/blob/main/README.md)
67
+
68
+ [![CI](https://github.com/warith-harchaoui/standingpoint/actions/workflows/ci.yml/badge.svg)](https://github.com/warith-harchaoui/standingpoint/actions/workflows/ci.yml) [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://github.com/warith-harchaoui/standingpoint/blob/main/LICENSE) [![Python](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue.svg)](#) [![Local-first](https://img.shields.io/badge/local--first-Ollama%20%2B%20Vega--Lite-brightgreen.svg)](#the-promise)
69
+
70
+ `Standpoint` belongs to a collection of libraries called `AI Helpers` developed for building Artificial Intelligence.
71
+
72
+ [๐ŸŒ AI Helpers](https://harchaoui.org/warith/ai-helpers)
73
+
74
+ [![Standpoint logo](https://raw.githubusercontent.com/warith-harchaoui/standingpoint/main/assets/logo.png)](https://harchaoui.org/warith/ai-helpers)
75
+
76
+ Know where each option actually stands.
77
+
78
+ Standpoint reads a comparison table (options as rows, criteria as columns, numbers
79
+ in the cells) and produces a 2D positioning map, a short written analysis, and a
80
+ YAML file with all the coordinates and coefficients. One command does it.
81
+
82
+ The method is ordinary PCA, which people have used for perceptual maps for a long
83
+ time. What Standpoint adds is the work you would otherwise do by hand: it orients
84
+ the map around a reference option, names the axes in plain words (in the language
85
+ of your columns), colours and labels the points, and writes everything out.
86
+
87
+ ## The Promise
88
+
89
+ Standpoint is **local-first** by design. Three honest cases:
90
+
91
+ 1. **Guaranteed local.** Parsing, PCA, orientation, colouring, and figure rendering
92
+ (via [`vl-convert`](https://github.com/vega/vl-convert)) all run on your machine.
93
+ Your table is **never uploaded**. There is **no telemetry, no account, no SaaS**.
94
+ 2. **The one caveat: the local model.** Axis names and the written analysis are
95
+ produced by a local [Ollama](https://ollama.com) model on `localhost`. Ollama
96
+ downloads the model weights **once** on first pull; after that it runs offline.
97
+ Nothing leaves your machine.
98
+ 3. **Your decision.** You never have to run the model at all: `--no-llm` gives you
99
+ the full map deterministically, with axis names taken from the strongest column
100
+ at each end and no written narrative.
101
+
102
+ ## Documentation
103
+
104
+ [๐Ÿ’ป Documentation](https://harchaoui.org/warith/ai-helpers/docs/standingpoint-doc/)
105
+
106
+ [๐Ÿ—บ๏ธ Landscape](https://github.com/warith-harchaoui/standingpoint/blob/main/LANDSCAPE.md)
107
+
108
+ [๐Ÿ“‹ Examples](https://github.com/warith-harchaoui/standingpoint/blob/main/EXAMPLES.md)
109
+
110
+ Input: a table of options and their ratings.
111
+
112
+ | Language | Performance | Ease of Learning | Ecosystem | Concurrency | Type Safety | Job Market | Tooling |
113
+ |---|---|---|---|---|---|---|---|
114
+ | Python | 2 | 5 | 5 | 2 | 2 | 5 | 4 |
115
+ | Rust | 5 | 2 | 3 | 5 | 5 | 3 | 4 |
116
+ | Go | 4 | 4 | 4 | 5 | 4 | 4 | 4 |
117
+ | JavaScript | 3 | 4 | 5 | 3 | 2 | 5 | 3 |
118
+ | โ€ฆ | | | | | | | |
119
+
120
+ Output: a positioning map,
121
+
122
+ ![Programming languages positioning map](https://raw.githubusercontent.com/warith-harchaoui/standingpoint/main/examples/programming_languages.png)
123
+
124
+ plus a Markdown analysis (what the axes mean, where the reference wins, which options
125
+ stand out, with the loadings and a ranking) and a YAML file with every option's
126
+ coordinates, role, colour, and original values.
127
+
128
+ ## Features
129
+
130
+ - **One command, three-fold deliverable**: a figure (PNG + SVG + Vega-Lite JSON), a
131
+ Markdown interpretation, and a YAML of coordinates + coefficients.
132
+ - **Readable axes**: PCA keeps the axes as weighted sums of your columns; a local
133
+ model names the four poles as positive qualities, guarded against acronyms,
134
+ negatives, and antonym pairs.
135
+ - **Multilingual**: axis names, the written analysis, and the figure title come out
136
+ in the table's own language (English, French, or Spanish), auto-detected from the
137
+ column names โ€” a French table reads *Voitures dans le quadrant*.
138
+ - **Reference-oriented**: the option you care about is rotated to the top-right; an
139
+ all-max reference is placed just past the best competitor rather than as an outlier.
140
+ - **Four highlighted options**: the leader, the weakest overall, and the two
141
+ challengers that reach furthest toward the top and right poles.
142
+ - **Polarity aware**: mark a lower-is-better column with `(โ†“)` (or `--lower`) and
143
+ Standpoint names the benefit (*Affordable*, *Portable*), never the drawback.
144
+ - **Deterministic fallback**: `--no-llm` needs no model and no network at all.
145
+ - **Vision self-check**: `--check` asks a local vision model whether the figure reads
146
+ correctly (leader top-right, labels legible, legend visible).
147
+
148
+ **Two surfaces, one toolkit** โ€” every operation is reachable as:
149
+
150
+ - **Library**: `import standpoint as sp`.
151
+ - **CLI ร—2**: `standpoint` (argparse, always installed) and `standpoint-click`
152
+ (click twin) with identical flags.
153
+
154
+ ## Installation
155
+
156
+ **Prerequisites** โ€” **Python 3.10โ€“3.13** and **git**, cross-platform:
157
+
158
+ - ๐ŸŽ **macOS** ([Homebrew](https://brew.sh)): `brew install python git`
159
+ - ๐Ÿง **Ubuntu/Debian**: `sudo apt update && sudo apt install -y python3 python3-pip git`
160
+ - ๐ŸชŸ **Windows** (PowerShell): `winget install Python.Python.3.12 Git.Git`
161
+
162
+ For axis names and the written analysis, install [Ollama](https://ollama.com) and pull
163
+ the default model once (optional โ€” skip it and use `--no-llm`):
164
+
165
+ - ๐ŸŽ **macOS**: `brew install ollama` โ€” then `ollama serve &` and `ollama pull qwen2.5vl:7b`
166
+ - ๐Ÿง **Ubuntu/Debian**: `curl -fsSL https://ollama.com/install.sh | sh` โ€” then `ollama pull qwen2.5vl:7b`
167
+ - ๐ŸชŸ **Windows**: install from [ollama.com/download](https://ollama.com/download), then `ollama pull qwen2.5vl:7b`
168
+
169
+ We recommend a Python environment. If you're new to that, see [๐Ÿฅธ Tech tips](https://harchaoui.org/warith/4ml/#install).
170
+
171
+ ### From PyPI (recommended)
172
+
173
+ ```bash
174
+ pip install standpoint
175
+ ```
176
+
177
+ ### From source
178
+
179
+ ```bash
180
+ git clone https://github.com/warith-harchaoui/standingpoint.git
181
+ cd standingpoint
182
+ pip install -e . # or: pip install -r requirements.txt
183
+ ```
184
+
185
+ Or install straight from GitHub (the import name is `standpoint`):
186
+
187
+ ```bash
188
+ pip install "git+https://github.com/warith-harchaoui/standingpoint.git@v0.2.0"
189
+ ```
190
+
191
+ ## Usage
192
+
193
+ ```bash
194
+ standpoint examples/programming_languages.csv --outdir out
195
+ # without installing: python3 -m standpoint examples/programming_languages.csv --outdir out
196
+ ```
197
+
198
+ Two equivalent CLIs are installed: `standpoint` (argparse) and `standpoint-click`.
199
+
200
+ As a library:
201
+
202
+ ```python
203
+ import standpoint as sp
204
+
205
+ pos = sp.positioning("examples/programming_languages.csv")
206
+ pos.export("out") # writes out/python.{png,svg,white.png,white.svg,vl.json,md,yaml}
207
+ print(pos.axes)
208
+ # {'x': 'Concurrency โ†” Ecosystem', 'y': 'Safety โ†” Learning'}
209
+ ```
210
+
211
+ Skip the model for a fast, deterministic run (no Ollama needed):
212
+
213
+ ```bash
214
+ standpoint my_table.csv --no-llm
215
+ standpoint my_table.csv --model qwen3:8b
216
+ ```
217
+
218
+ More in [EXAMPLES.md](https://github.com/warith-harchaoui/standingpoint/blob/main/EXAMPLES.md).
219
+
220
+ ## Input format
221
+
222
+ A CSV or Markdown table. The first column holds the option names; the rest are
223
+ numeric criteria on any scale. Higher means better. Empty cells are filled with
224
+ the column's minimum, so a missing rating never helps an option.
225
+
226
+ | Language | Performance | Ease of Learning | Ecosystem | Type Safety | Job Market |
227
+ |---|---|---|---|---|---|
228
+ | Python | 2 | 5 | 5 | 2 | 5 |
229
+ | Rust | 5 | 2 | 3 | 5 | 3 |
230
+ | Go | 4 | 4 | 4 | 4 | 4 |
231
+
232
+ The first row is the reference and goes to the top right. Change it with
233
+ `--reference "<name>"`. Mark a lower-is-better column with `(โ†“)`, e.g.
234
+ `Price (โ†“)`, or list it in `--lower`.
235
+
236
+ ## How it works
237
+
238
+ 1. Standardize each criterion to mean 0 and standard deviation 1. PCA is sensitive
239
+ to scale, so this puts every criterion on equal footing.
240
+ 2. Run PCA and keep two components. The axes stay as weighted sums of the original
241
+ columns, so you can read them.
242
+ 3. Rotate the map so the reference sits top right. If the reference scores top
243
+ marks on everything, it is placed just past the best competitor on each axis
244
+ rather than far off on its own.
245
+ 4. Label it. The four highlighted options (leader, weakest, and the two challengers
246
+ furthest toward the top and right poles) come straight from the map geometry.
247
+ Each option takes its own colour from its position. A local model reads the
248
+ loadings and names the four axis ends, as positive qualities, in your columns'
249
+ language (English, French, or Spanish).
250
+
251
+ The figure keeps to a dotted cross for the axes, the pole words at the ends, labels
252
+ only where they fit, and a legend for the rest.
253
+
254
+ ![Electric cars, French input gives a French title and French axis names](https://raw.githubusercontent.com/warith-harchaoui/standingpoint/main/examples/voitures_electriques.png)
255
+
256
+ ## Notes
257
+
258
+ - Axis names come from a local model. A guard keeps them positive, distinct, and
259
+ free of acronyms; a larger `--model` helps, and `--check` asks the vision model
260
+ whether the figure reads correctly.
261
+ - Higher is better by default. For a column where lower is better, mark its header
262
+ with `(โ†“)` (`Price (โ†“)`, `Latency (โ†“)`) or pass `--lower Price,Latency`. Standpoint
263
+ negates it and names the pole for the benefit ("Affordable", "Portable"), never
264
+ the drawback.
265
+ - Every figure is written twice: a **transparent** `.png` / `.svg` that drops onto any
266
+ page, and a **white-background** `.white.png` / `.white.svg` for dark surfaces where
267
+ the near-black labels would otherwise vanish on transparency.
268
+ - It is a 2D projection. The axes carry a stated fraction of the variance, so read
269
+ it as a summary rather than the whole picture.
270
+
271
+ ## Examples
272
+
273
+ Tracked in `examples/`, input CSV and generated figures:
274
+
275
+ | Table | Language | Leader |
276
+ |---|---|---|
277
+ | `programming_languages.csv` | en | Python |
278
+ | `cloud_providers.csv` | en | AWS |
279
+ | `laptops.csv` | en | MacBook Air (uses `Price (โ†“)` / `Weight (โ†“)`) |
280
+ | `voitures_electriques.csv` | fr | Tesla Model 3 |
281
+
282
+ ## Development
283
+
284
+ ```bash
285
+ pip install -r requirements-dev.txt # or: pip install -e ".[dev]"
286
+ python3 -m pytest tests/ -q # deterministic tests; model-backed ones auto-skip
287
+ python3 -m ruff check standpoint tests
288
+ python3 -m ruff format --check standpoint tests
289
+ ```
290
+
291
+ The coding standard for this repository is [CODING.md](https://github.com/warith-harchaoui/standingpoint/blob/main/CODING.md);
292
+ the contribution and versioning policy is in [CONTRIBUTING.md](https://github.com/warith-harchaoui/standingpoint/blob/main/CONTRIBUTING.md).
293
+
294
+ ## Credits
295
+
296
+ PCA perceptual maps are standard (`factoextra` and `FactoMineR` in R, `prince` and
297
+ `pca` in Python); using a model to read the components is a newer idea. Colours
298
+ come from the ["Good Colors"](https://harchaoui.org/warith/colors/) palette.
299
+ Figures are rendered by [`vl-convert`](https://github.com/vega/vl-convert) over
300
+ [Vega-Lite](https://vega.github.io/vega-lite/).
301
+
302
+ ## Author
303
+
304
+ [Warith Harchaoui](https://www.linkedin.com/in/warith-harchaoui)
305
+
306
+ ## License
307
+
308
+ BSD 3-Clause, the same license as scikit-learn. See
309
+ [`LICENSE`](https://github.com/warith-harchaoui/standingpoint/blob/main/LICENSE).
@@ -0,0 +1,246 @@
1
+ # Standpoint
2
+
3
+ [๐Ÿ‡ซ๐Ÿ‡ท](https://github.com/warith-harchaoui/standingpoint/blob/main/LISEZMOI.md) ยท [๐Ÿ‡ฌ๐Ÿ‡ง](https://github.com/warith-harchaoui/standingpoint/blob/main/README.md)
4
+
5
+ [![CI](https://github.com/warith-harchaoui/standingpoint/actions/workflows/ci.yml/badge.svg)](https://github.com/warith-harchaoui/standingpoint/actions/workflows/ci.yml) [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://github.com/warith-harchaoui/standingpoint/blob/main/LICENSE) [![Python](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue.svg)](#) [![Local-first](https://img.shields.io/badge/local--first-Ollama%20%2B%20Vega--Lite-brightgreen.svg)](#the-promise)
6
+
7
+ `Standpoint` belongs to a collection of libraries called `AI Helpers` developed for building Artificial Intelligence.
8
+
9
+ [๐ŸŒ AI Helpers](https://harchaoui.org/warith/ai-helpers)
10
+
11
+ [![Standpoint logo](https://raw.githubusercontent.com/warith-harchaoui/standingpoint/main/assets/logo.png)](https://harchaoui.org/warith/ai-helpers)
12
+
13
+ Know where each option actually stands.
14
+
15
+ Standpoint reads a comparison table (options as rows, criteria as columns, numbers
16
+ in the cells) and produces a 2D positioning map, a short written analysis, and a
17
+ YAML file with all the coordinates and coefficients. One command does it.
18
+
19
+ The method is ordinary PCA, which people have used for perceptual maps for a long
20
+ time. What Standpoint adds is the work you would otherwise do by hand: it orients
21
+ the map around a reference option, names the axes in plain words (in the language
22
+ of your columns), colours and labels the points, and writes everything out.
23
+
24
+ ## The Promise
25
+
26
+ Standpoint is **local-first** by design. Three honest cases:
27
+
28
+ 1. **Guaranteed local.** Parsing, PCA, orientation, colouring, and figure rendering
29
+ (via [`vl-convert`](https://github.com/vega/vl-convert)) all run on your machine.
30
+ Your table is **never uploaded**. There is **no telemetry, no account, no SaaS**.
31
+ 2. **The one caveat: the local model.** Axis names and the written analysis are
32
+ produced by a local [Ollama](https://ollama.com) model on `localhost`. Ollama
33
+ downloads the model weights **once** on first pull; after that it runs offline.
34
+ Nothing leaves your machine.
35
+ 3. **Your decision.** You never have to run the model at all: `--no-llm` gives you
36
+ the full map deterministically, with axis names taken from the strongest column
37
+ at each end and no written narrative.
38
+
39
+ ## Documentation
40
+
41
+ [๐Ÿ’ป Documentation](https://harchaoui.org/warith/ai-helpers/docs/standingpoint-doc/)
42
+
43
+ [๐Ÿ—บ๏ธ Landscape](https://github.com/warith-harchaoui/standingpoint/blob/main/LANDSCAPE.md)
44
+
45
+ [๐Ÿ“‹ Examples](https://github.com/warith-harchaoui/standingpoint/blob/main/EXAMPLES.md)
46
+
47
+ Input: a table of options and their ratings.
48
+
49
+ | Language | Performance | Ease of Learning | Ecosystem | Concurrency | Type Safety | Job Market | Tooling |
50
+ |---|---|---|---|---|---|---|---|
51
+ | Python | 2 | 5 | 5 | 2 | 2 | 5 | 4 |
52
+ | Rust | 5 | 2 | 3 | 5 | 5 | 3 | 4 |
53
+ | Go | 4 | 4 | 4 | 5 | 4 | 4 | 4 |
54
+ | JavaScript | 3 | 4 | 5 | 3 | 2 | 5 | 3 |
55
+ | โ€ฆ | | | | | | | |
56
+
57
+ Output: a positioning map,
58
+
59
+ ![Programming languages positioning map](https://raw.githubusercontent.com/warith-harchaoui/standingpoint/main/examples/programming_languages.png)
60
+
61
+ plus a Markdown analysis (what the axes mean, where the reference wins, which options
62
+ stand out, with the loadings and a ranking) and a YAML file with every option's
63
+ coordinates, role, colour, and original values.
64
+
65
+ ## Features
66
+
67
+ - **One command, three-fold deliverable**: a figure (PNG + SVG + Vega-Lite JSON), a
68
+ Markdown interpretation, and a YAML of coordinates + coefficients.
69
+ - **Readable axes**: PCA keeps the axes as weighted sums of your columns; a local
70
+ model names the four poles as positive qualities, guarded against acronyms,
71
+ negatives, and antonym pairs.
72
+ - **Multilingual**: axis names, the written analysis, and the figure title come out
73
+ in the table's own language (English, French, or Spanish), auto-detected from the
74
+ column names โ€” a French table reads *Voitures dans le quadrant*.
75
+ - **Reference-oriented**: the option you care about is rotated to the top-right; an
76
+ all-max reference is placed just past the best competitor rather than as an outlier.
77
+ - **Four highlighted options**: the leader, the weakest overall, and the two
78
+ challengers that reach furthest toward the top and right poles.
79
+ - **Polarity aware**: mark a lower-is-better column with `(โ†“)` (or `--lower`) and
80
+ Standpoint names the benefit (*Affordable*, *Portable*), never the drawback.
81
+ - **Deterministic fallback**: `--no-llm` needs no model and no network at all.
82
+ - **Vision self-check**: `--check` asks a local vision model whether the figure reads
83
+ correctly (leader top-right, labels legible, legend visible).
84
+
85
+ **Two surfaces, one toolkit** โ€” every operation is reachable as:
86
+
87
+ - **Library**: `import standpoint as sp`.
88
+ - **CLI ร—2**: `standpoint` (argparse, always installed) and `standpoint-click`
89
+ (click twin) with identical flags.
90
+
91
+ ## Installation
92
+
93
+ **Prerequisites** โ€” **Python 3.10โ€“3.13** and **git**, cross-platform:
94
+
95
+ - ๐ŸŽ **macOS** ([Homebrew](https://brew.sh)): `brew install python git`
96
+ - ๐Ÿง **Ubuntu/Debian**: `sudo apt update && sudo apt install -y python3 python3-pip git`
97
+ - ๐ŸชŸ **Windows** (PowerShell): `winget install Python.Python.3.12 Git.Git`
98
+
99
+ For axis names and the written analysis, install [Ollama](https://ollama.com) and pull
100
+ the default model once (optional โ€” skip it and use `--no-llm`):
101
+
102
+ - ๐ŸŽ **macOS**: `brew install ollama` โ€” then `ollama serve &` and `ollama pull qwen2.5vl:7b`
103
+ - ๐Ÿง **Ubuntu/Debian**: `curl -fsSL https://ollama.com/install.sh | sh` โ€” then `ollama pull qwen2.5vl:7b`
104
+ - ๐ŸชŸ **Windows**: install from [ollama.com/download](https://ollama.com/download), then `ollama pull qwen2.5vl:7b`
105
+
106
+ We recommend a Python environment. If you're new to that, see [๐Ÿฅธ Tech tips](https://harchaoui.org/warith/4ml/#install).
107
+
108
+ ### From PyPI (recommended)
109
+
110
+ ```bash
111
+ pip install standpoint
112
+ ```
113
+
114
+ ### From source
115
+
116
+ ```bash
117
+ git clone https://github.com/warith-harchaoui/standingpoint.git
118
+ cd standingpoint
119
+ pip install -e . # or: pip install -r requirements.txt
120
+ ```
121
+
122
+ Or install straight from GitHub (the import name is `standpoint`):
123
+
124
+ ```bash
125
+ pip install "git+https://github.com/warith-harchaoui/standingpoint.git@v0.2.0"
126
+ ```
127
+
128
+ ## Usage
129
+
130
+ ```bash
131
+ standpoint examples/programming_languages.csv --outdir out
132
+ # without installing: python3 -m standpoint examples/programming_languages.csv --outdir out
133
+ ```
134
+
135
+ Two equivalent CLIs are installed: `standpoint` (argparse) and `standpoint-click`.
136
+
137
+ As a library:
138
+
139
+ ```python
140
+ import standpoint as sp
141
+
142
+ pos = sp.positioning("examples/programming_languages.csv")
143
+ pos.export("out") # writes out/python.{png,svg,white.png,white.svg,vl.json,md,yaml}
144
+ print(pos.axes)
145
+ # {'x': 'Concurrency โ†” Ecosystem', 'y': 'Safety โ†” Learning'}
146
+ ```
147
+
148
+ Skip the model for a fast, deterministic run (no Ollama needed):
149
+
150
+ ```bash
151
+ standpoint my_table.csv --no-llm
152
+ standpoint my_table.csv --model qwen3:8b
153
+ ```
154
+
155
+ More in [EXAMPLES.md](https://github.com/warith-harchaoui/standingpoint/blob/main/EXAMPLES.md).
156
+
157
+ ## Input format
158
+
159
+ A CSV or Markdown table. The first column holds the option names; the rest are
160
+ numeric criteria on any scale. Higher means better. Empty cells are filled with
161
+ the column's minimum, so a missing rating never helps an option.
162
+
163
+ | Language | Performance | Ease of Learning | Ecosystem | Type Safety | Job Market |
164
+ |---|---|---|---|---|---|
165
+ | Python | 2 | 5 | 5 | 2 | 5 |
166
+ | Rust | 5 | 2 | 3 | 5 | 3 |
167
+ | Go | 4 | 4 | 4 | 4 | 4 |
168
+
169
+ The first row is the reference and goes to the top right. Change it with
170
+ `--reference "<name>"`. Mark a lower-is-better column with `(โ†“)`, e.g.
171
+ `Price (โ†“)`, or list it in `--lower`.
172
+
173
+ ## How it works
174
+
175
+ 1. Standardize each criterion to mean 0 and standard deviation 1. PCA is sensitive
176
+ to scale, so this puts every criterion on equal footing.
177
+ 2. Run PCA and keep two components. The axes stay as weighted sums of the original
178
+ columns, so you can read them.
179
+ 3. Rotate the map so the reference sits top right. If the reference scores top
180
+ marks on everything, it is placed just past the best competitor on each axis
181
+ rather than far off on its own.
182
+ 4. Label it. The four highlighted options (leader, weakest, and the two challengers
183
+ furthest toward the top and right poles) come straight from the map geometry.
184
+ Each option takes its own colour from its position. A local model reads the
185
+ loadings and names the four axis ends, as positive qualities, in your columns'
186
+ language (English, French, or Spanish).
187
+
188
+ The figure keeps to a dotted cross for the axes, the pole words at the ends, labels
189
+ only where they fit, and a legend for the rest.
190
+
191
+ ![Electric cars, French input gives a French title and French axis names](https://raw.githubusercontent.com/warith-harchaoui/standingpoint/main/examples/voitures_electriques.png)
192
+
193
+ ## Notes
194
+
195
+ - Axis names come from a local model. A guard keeps them positive, distinct, and
196
+ free of acronyms; a larger `--model` helps, and `--check` asks the vision model
197
+ whether the figure reads correctly.
198
+ - Higher is better by default. For a column where lower is better, mark its header
199
+ with `(โ†“)` (`Price (โ†“)`, `Latency (โ†“)`) or pass `--lower Price,Latency`. Standpoint
200
+ negates it and names the pole for the benefit ("Affordable", "Portable"), never
201
+ the drawback.
202
+ - Every figure is written twice: a **transparent** `.png` / `.svg` that drops onto any
203
+ page, and a **white-background** `.white.png` / `.white.svg` for dark surfaces where
204
+ the near-black labels would otherwise vanish on transparency.
205
+ - It is a 2D projection. The axes carry a stated fraction of the variance, so read
206
+ it as a summary rather than the whole picture.
207
+
208
+ ## Examples
209
+
210
+ Tracked in `examples/`, input CSV and generated figures:
211
+
212
+ | Table | Language | Leader |
213
+ |---|---|---|
214
+ | `programming_languages.csv` | en | Python |
215
+ | `cloud_providers.csv` | en | AWS |
216
+ | `laptops.csv` | en | MacBook Air (uses `Price (โ†“)` / `Weight (โ†“)`) |
217
+ | `voitures_electriques.csv` | fr | Tesla Model 3 |
218
+
219
+ ## Development
220
+
221
+ ```bash
222
+ pip install -r requirements-dev.txt # or: pip install -e ".[dev]"
223
+ python3 -m pytest tests/ -q # deterministic tests; model-backed ones auto-skip
224
+ python3 -m ruff check standpoint tests
225
+ python3 -m ruff format --check standpoint tests
226
+ ```
227
+
228
+ The coding standard for this repository is [CODING.md](https://github.com/warith-harchaoui/standingpoint/blob/main/CODING.md);
229
+ the contribution and versioning policy is in [CONTRIBUTING.md](https://github.com/warith-harchaoui/standingpoint/blob/main/CONTRIBUTING.md).
230
+
231
+ ## Credits
232
+
233
+ PCA perceptual maps are standard (`factoextra` and `FactoMineR` in R, `prince` and
234
+ `pca` in Python); using a model to read the components is a newer idea. Colours
235
+ come from the ["Good Colors"](https://harchaoui.org/warith/colors/) palette.
236
+ Figures are rendered by [`vl-convert`](https://github.com/vega/vl-convert) over
237
+ [Vega-Lite](https://vega.github.io/vega-lite/).
238
+
239
+ ## Author
240
+
241
+ [Warith Harchaoui](https://www.linkedin.com/in/warith-harchaoui)
242
+
243
+ ## License
244
+
245
+ BSD 3-Clause, the same license as scikit-learn. See
246
+ [`LICENSE`](https://github.com/warith-harchaoui/standingpoint/blob/main/LICENSE).
@@ -0,0 +1,75 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "standpoint"
7
+ description = "Know where each option stands: a labelled 2D positioning map from any comparison table."
8
+ readme = "README.md"
9
+ requires-python = ">=3.10"
10
+ license = { file = "LICENSE" }
11
+ authors = [{ name = "Warith Harchaoui" }]
12
+ keywords = ["pca", "positioning map", "perceptual map", "quadrant", "vega-lite"]
13
+ dynamic = ["version"]
14
+ dependencies = [
15
+ "numpy>=1.24",
16
+ "pandas>=2.0",
17
+ "scikit-learn>=1.3",
18
+ "vl-convert-python>=1.0",
19
+ "PyYAML>=6.0",
20
+ "langdetect>=1.0.9",
21
+ "ollama>=0.3",
22
+ "click>=8.1",
23
+ ]
24
+ classifiers = [
25
+ "License :: OSI Approved :: BSD License",
26
+ "Programming Language :: Python :: 3",
27
+ "Programming Language :: Python :: 3.10",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Programming Language :: Python :: 3.13",
31
+ "Topic :: Scientific/Engineering :: Visualization",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ # Testing + linting kept light so CI installs fast.
36
+ dev = ["pytest>=7.0", "ruff>=0.5"]
37
+ # `deepeval` powers the optional pole-naming evaluation in tests/test_eval.py. It is
38
+ # a separate, heavier extra: the eval is skipped unless both deepeval and a local
39
+ # Ollama model are available, so it runs on a workstation, not in the default CI job.
40
+ eval = ["deepeval>=1.0"]
41
+
42
+ [project.urls]
43
+ Homepage = "https://github.com/warith-harchaoui/standingpoint"
44
+ Author = "https://www.linkedin.com/in/warith-harchaoui"
45
+
46
+ [project.scripts]
47
+ standpoint = "standpoint:main"
48
+ standpoint-click = "standpoint.click_cli:main_click"
49
+
50
+ [tool.setuptools]
51
+ packages = ["standpoint"]
52
+
53
+ [tool.setuptools.dynamic]
54
+ version = { attr = "standpoint.__version__" }
55
+
56
+ [tool.setuptools.package-data]
57
+ standpoint = ["i18n.yaml"]
58
+
59
+ [tool.ruff]
60
+ # One source of truth for lint + format (Rule 17). 100 cols matches the prose-heavy
61
+ # docstrings; target the oldest supported interpreter so no newer syntax slips in.
62
+ line-length = 100
63
+ target-version = "py310"
64
+
65
+ [tool.ruff.lint]
66
+ # Conservative, high-signal set: pyflakes (F), pycodestyle errors/warnings (E/W),
67
+ # isort (I), bugbear (B), comprehensions (C4), pyupgrade (UP), simplify (SIM).
68
+ select = ["E", "F", "W", "I", "B", "C4", "UP", "SIM"]
69
+ ignore = [
70
+ "E501", # line length is enforced by the formatter, not the linter
71
+ "SIM108", # ternary-over-if/else sometimes hurts readability
72
+ ]
73
+
74
+ [tool.ruff.lint.per-file-ignores]
75
+ "tests/*" = ["B011"] # `assert False` is idiomatic in pytest
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+