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