Skip to content

Commit 141d634

Browse files
committed
Split user guides into practical task-focused pages
Introduce short runnable starting pages for numerical data, plotting and curve fitting. Move detailed topics into 21 smaller guides, simplify navigation, preserve worked examples and named references, and remove outdated video links and the plotting autodoc block from the user-facing guide. Validation: new starter and editing recipes executed successfully; plotting, layouts, histograms, fitting, rendering and saving checked locally. Sphinx HTML builds with no new warning messages compared with the previous build; 24 pages and 781 navigation links checked, no broken navigation or removed API inventory entries. Retained plot caches unchanged.
1 parent e4cd661 commit 141d634

28 files changed

Lines changed: 3026 additions & 2374 deletions

‎doc/UserGuide/curve_fitting.rst‎

Lines changed: 51 additions & 487 deletions
Large diffs are not rendered by default.

‎doc/UserGuide/data_columns.rst‎

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
Choosing columns and assigning roles
2+
************************************
3+
4+
.. currentmodule:: Stoner
5+
6+
Column names tell you what was measured. Column roles tell Stoner which columns
7+
to use as coordinates, signals and uncertainties when plotting or fitting.
8+
You can select a column by its name or by its position; positions start at zero.
9+
10+
For a three-column measurement containing temperature, resistance and resistance
11+
uncertainty, start with::
12+
13+
d.column_headers = ["Temperature", "Resistance", "Uncertainty"]
14+
d.setas = "xye"
15+
temperature = d.column("Temperature")
16+
resistance = d.y
17+
18+
Here ``x`` is the horizontal coordinate, ``y`` the measured signal, and ``e`` its
19+
uncertainty. Use the example in :doc:`datafile` if you do not yet have a Data
20+
object. Later examples illustrate alternative layouts, including tables with
21+
five or more columns; adapt them to the columns in your own measurement.
22+
23+
.. contents:: On this page
24+
:local:
25+
:depth: 1
26+
27+
.. _setas:
28+
29+
Assign column roles with setas
30+
==============================
31+
32+
Assign one character to each column, in order. Unmarked columns still contain
33+
data; Stoner simply does not give them a coordinate or uncertainty role.
34+
35+
====================== ================================================
36+
Role Meaning
37+
====================== ================================================
38+
``x``, ``y``, ``z`` Coordinates or measured signals
39+
``d``, ``e``, ``f`` Uncertainties in x, y and z, respectively
40+
``u``, ``v``, ``w`` Vector components at a coordinate (x, y, z)
41+
``.`` Unmarked column
42+
``-`` Leave the existing assignment unchanged
43+
====================== ================================================
44+
45+
Many plotting and analysis methods use these assignments as their defaults.
46+
Vector-component uncertainties do not have assigned role letters.
47+
48+
Setting Column Types
49+
--------------------
50+
51+
To set which columns contain 'x','y' etc data use the :py:attr:`~Stoner.core.data.Data.setas` attribute. This attribute can take
52+
a list of single character strings from the set 'x','y','z','d','e', 'f', 'u', 'v', 'w' or '.' where each element of the list refers to
53+
the columns of data in order. To specify that a column has unmarked data use the '.' string. The string '-' can also be used - this
54+
indicates that the current assignment is to be left as is.
55+
56+
Alternately, you can pass :py:attr:`~Stoner.core.data.Data.setas` a string. In the simplest case, the string is just read in the same way that
57+
the list would have been - each character corresponds to one column. However, if the string contains an integer, then the next
58+
non-numeric character will be interpreted that many times, so::
59+
60+
d.setas="3.xy"
61+
d.setas="...xy"
62+
d.setas=['.','.','.','x','y']
63+
64+
There are still more ways of setting column types with the :py:attr:`~Stoner.core.data.Data.setas` attribute::
65+
66+
d.setas[3]="x"
67+
d.setas["x"]=3
68+
d.setas("3.xy")
69+
d.setas(['.','.','.','x','y'])
70+
d.setas(x=3,y=4)
71+
72+
All achieve the same effect of setting the same columns as containing x and y data.
73+
74+
Once you have identified columns for the various types, you also have access to utility attributes to access those columns::
75+
76+
d.setas="3.xye"
77+
d.x == d.column(3)
78+
d.y == d.column(4)
79+
d.e == d.column(5)
80+
81+
Note that if :py:attr:`~Stoner.core.data.Data.setas` is not set then attempting to use the quick access column attributes will
82+
result in an exception. Once the :py:attr:`~Stoner.core.data.Data.setas` attribute is set, a further set of *virtual* or *derived* column attributes
83+
become available::
84+
85+
d.setas="xyz"
86+
d.r # The magnitude of the point x,y,z
87+
d.q # The angle in the x-y plane relative to the x axis
88+
d.p # The angle relative to the z axis
89+
d.setas="xyz..uvw"
90+
d.r # the magnitude of (u,v,w)
91+
d.q # The angle in the x-y plane of (u,v) relative to +x
92+
d.p # The angle of (u,v,w) relative to +z
93+
94+
Where fewer than 3 or 6 dimensions are specified, these virtual columns fall back to working with the appropriate
95+
reduced number of dimensions.
96+
97+
There are some more convenience ways to set which columns to use as x,y,z etc::
98+
99+
d.setas={"x":0,"y":"Y Column title"}
100+
d.x=0
101+
d.y="Y Column title"
102+
d.setas["Temperature"]="y"
103+
104+
In each of these cases, the :py:class:`~Stoner.core.data.Data` will try to work out what you intended to achieve for maximum flexibility
105+
and convenience when writing code. However it can be fooled if one of your columns is called 'x' or 'y' !
106+
107+
Reading Column Types
108+
--------------------
109+
110+
The normal representation of :py:attr:`~Stoner.core.data.Data.setas` is as a list, but it also has a string conversion available. You can also find which column
111+
has been assigned as 'x', 'y' etc. by treating the :py:attr:`~Stoner.core.data.Data.setas` as a dictionary::
112+
113+
d.column_headers=["One","Two","three","Four"]
114+
d.setas="xy.z"
115+
116+
print(list(d.setas)) #['x','y','.','z']
117+
print(d.setas) #'xy.z'
118+
print(d.setas['x']) # "One"
119+
print(d.setas["#x"]) # 0
120+
121+
Note that the :py:attr:`~Stoner.core.data.Data.setas` attribute supports reading keys that are either the single letter to get the name of the column or the letter
122+
preceded by a # character to get the number of the column.
123+
124+
Alternatively, and equivalently, you can access the column indexes via attributes of :py:attr:`~Stoner.core.data.Data.setas`:
125+
126+
d.setas.has_xcol # True
127+
d.setas.has_ucol # False
128+
d.setas.ycol # [1]
129+
d.setas.xcol # 0
130+
131+
Implied Column Assignments
132+
--------------------------
133+
134+
If you do not specify the column types via the setas attributes, then :py:class:`~Stoner.core.data.Data` will try to guess sensible columns assignments based on the number
135+
of columns in your data file. These default assignments are only done at the point at which the :py:attr:`~Stoner.core.data.Data.setas` attribute is consulted. The default
136+
assignments are:
137+
138+
================= ================
139+
Number of Columns Assignments
140+
================= ================
141+
2 x, y
142+
3 x, y, e
143+
4 x, d, y, e
144+
5 x, y, u, v, w
145+
6 x, y, z, u, v, w
146+
================= ================
147+
148+
Swapping and Rotating Column Assignments
149+
----------------------------------------
150+
151+
Finally, if the :py:attr:`~Stoner.core.data.Data.setas` attribute has been set with *x*, *y* (and *z*) columns then these assignments can be
152+
swapped around by the **invert** operator **~**. This either swaps *x* and *y* with their associated error bars for 2-D datasets, or rotates
153+
*x* to *y*, *y* to *z* and *z* to *x* (again with their associated error bars)::
154+
155+
d.setas="xye"
156+
print(d.setas)
157+
>>> ['x','y','e']
158+
e=~d
159+
print(e.setas)
160+
>>> ['y','x','d']
161+
162+
Read columns by name or position
163+
================================
164+
165+
This is all very well, but often you want to examine a particular column of data
166+
or a particular row::
167+
168+
d.column(0)
169+
d.column('Temperature')
170+
d.column(['Temperature',0])
171+
172+
In the first example, the first column of numeric data will be returned. In the
173+
second example, the column headers will first be checked for one labelled exactly
174+
*Temperature* and then if no column is found, the column headers will be
175+
searched using *Temperature* as a regular expression. This would then
176+
match *Temperature (K)* or *Sample Temperature*. The third
177+
example results in a 2 dimensional numpy array containing two columns in the
178+
order that they appear in the list (ie not the order that they are in the data
179+
file). For completeness, the :py:meth:`~Stoner.core.data.Data.column` method also allows one to
180+
pass slices to select columns and should do the expected thing.
181+
182+
There are a couple of convenient short cuts. Firstly the *floor-division* operator //
183+
is an alias for the :py:meth:`~Stoner.core.data.Data.column` method and secondly for working
184+
with cases where the column headers are not the same as the names of any of the attributes
185+
of the :py:class:`~Stoner.core.data.Data` object::
186+
187+
d//"Temperature"
188+
d.Temperature
189+
d.column('Temperature')
190+
191+
all return the same data.
192+
193+
Whenever the Stoner package needs to refer to a column of data, you can specify it in a number of ways:-
194+
195+
1) As an integer where the first column on the left is index 0
196+
2) As a string. if the string matches a column header exactly then the index of that column is returned.
197+
If the string fails to match any column header it is compiled as a regular expression and then that
198+
is tried as a match. If multiple columns match then only the first is returned.
199+
3) As a regular expression directly - this is similar to the case above with a string, but allows you to pass a pre-compiled regular
200+
expression in directly with any extra options (like case insensitivity flags) set.
201+
4) As a slice object (e.g. ``0:10:2``) this is expanded to a list of integers.
202+
5) As a list of any of the above, in which case the column finding routine is called recursively in turn for each element of the list and
203+
the final result would be to use a list of column indices.
204+
205+
For example::
206+
import re
207+
col=re.compile('^temp',re.IGNORECASE)
208+
d.column(col)
209+
210+
Advanced: patterns, column indices and iteration
211+
================================================
212+
213+
Many methods in Stoner index columns by searching the column
214+
headings. If one wishes to find the numeric index of a column then the
215+
:py:meth:`~Stoner.core.data.Data.find_col` method can be used::
216+
217+
index=d.find_col(1)
218+
index=d.find_col('Temperature')
219+
index=d.find_col('Temp.*')
220+
index=d.find_col('1')
221+
index=d.find_col(slice(1, 10, 2))
222+
index=d.find_col(['Temperature',2,'Resistance'])
223+
index=d.find_col(re.compile(r"^[A-Z]"))
224+
225+
:py:meth:`~Stoner.core.data.Data.find_col` takes a number of different forms. If the argument
226+
is an integer then it returns (trivially) the same integer, a string argument is
227+
first checked to see if it exactly matches one of the column headers in which
228+
case the number of the matching column heading is returned. If no exact match is
229+
found then a regular expression search is carried out on the column headings. In
230+
both cases, only the first match is returned. If there is no match, a ``KeyError`` is raised. A numeric string such as
231+
``'1'`` selects a header or pattern; use the integer ``1`` to select by position.
232+
233+
The final three examples given above
234+
both return a list of indices, firstly using a slice construct - in this case
235+
the slice is expanded to the corresponding column positions, and in the second example by
236+
passing a list of column headers to look for. The final example uses a compiled
237+
regular expression. Unlike passing a string which contains a regular expression,
238+
passing a compiled regular expression will return a list of all columns that
239+
match. This distinction allows you to use a unique partial string to match just
240+
one column - but if you really want all possible columns that would match the
241+
pattern, then you can compile the regular expression and pass that instead.
242+
243+
This is the function that is used internally by :py:meth:`~Stoner.core.data.Data.column`,
244+
:py:meth:`~Stoner.core.data.Data.search` etc and for this reason the trivial integer and slice
245+
forms are implemented to allow these other functions to work with multiple
246+
columns.
247+
248+
Sometimes you may want to iterate over all of the rows or columns in a data set.
249+
This can be done quite easily::
250+
251+
for row in d.rows():
252+
print(row)
253+
254+
for column in d.columns():
255+
print(column)
256+
257+
If there is no mask set, then the first example could also have been written more compactly as::
258+
259+
for row in d:
260+
print(row)
261+
262+
.. note::
263+
264+
:py:meth:`~Stoner.core.data.Data.rows` and :py:meth:`~Stoner.core.data.Data.columns` both take an optional parameter *not_masked*. If this is True then these iterator
265+
methods will skip over any rows/columns with masked out data values. Direct iteration includes masked rows; use ``d.rows(not_masked=True)``
266+
when you want to skip rows containing masked cells.
267+
268+
Next steps
269+
==========
270+
271+
Continue with :doc:`data_indexing` to read individual cells and slices.
272+
To draw your measurement now, continue with :doc:`plotfile`.
273+
Return to :doc:`datafile` for the full learning path.

0 commit comments

Comments
 (0)