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.
- standpoint-0.2.0/LICENSE +29 -0
- standpoint-0.2.0/PKG-INFO +309 -0
- standpoint-0.2.0/README.md +246 -0
- standpoint-0.2.0/pyproject.toml +75 -0
- standpoint-0.2.0/setup.cfg +4 -0
- standpoint-0.2.0/standpoint/__init__.py +1699 -0
- standpoint-0.2.0/standpoint/__main__.py +4 -0
- standpoint-0.2.0/standpoint/click_cli.py +59 -0
- standpoint-0.2.0/standpoint/i18n.yaml +146 -0
- standpoint-0.2.0/standpoint.egg-info/PKG-INFO +309 -0
- standpoint-0.2.0/standpoint.egg-info/SOURCES.txt +15 -0
- standpoint-0.2.0/standpoint.egg-info/dependency_links.txt +1 -0
- standpoint-0.2.0/standpoint.egg-info/entry_points.txt +3 -0
- standpoint-0.2.0/standpoint.egg-info/requires.txt +15 -0
- standpoint-0.2.0/standpoint.egg-info/top_level.txt +1 -0
- standpoint-0.2.0/tests/test_eval.py +111 -0
- standpoint-0.2.0/tests/test_standpoint.py +386 -0
standpoint-0.2.0/LICENSE
ADDED
|
@@ -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
|
+
[](https://github.com/warith-harchaoui/standingpoint/actions/workflows/ci.yml) [](https://github.com/warith-harchaoui/standingpoint/blob/main/LICENSE) [](#) [](#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
|
+
[](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
|
+

|
|
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
|
+

|
|
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
|
+
[](https://github.com/warith-harchaoui/standingpoint/actions/workflows/ci.yml) [](https://github.com/warith-harchaoui/standingpoint/blob/main/LICENSE) [](#) [](#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
|
+
[](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
|
+

|
|
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
|
+

|
|
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
|