Slices(3pm)
π NAME π
PDL::Slices β Indexing, slicing, and dicing. Yeah bestie, that's the whole vibe. This is the Slices(3pm) page and it's lowkey the backbone of everything cool you'll ever do with PDL.
β¨ SYNOPSIS β¨
use PDL;
$x = ones(3,3);
$y = $x->slice('-1:0,(1)');
$c = $x->dummy(2);
π¦ DESCRIPTION π¦
okay so. This package hands you a whole bunch of the powerful PerlDL core index manipulation routines, and they're iconic. The way most of these work is they allow two-way data flow, which means you can mess with your data in whatever representation is most convenient and it just works. For example, you can make a 1000x1000 unit matrix like this:
$x = zeroes(1000,1000);
$x->diagonal(0,1) ++;
and that's genuinely quite efficient. no cap. Wanna see more examples? Peep PDL::Indexing and PDL::Tips.
Slicing is so central to the PDL language that there's a whole special compile-time syntax invented just to handle it compactly β go read PDL::NiceSlice for the details, it's giving convenience.
PDL indexing and slicing functions usually include that two-way data flow, so you can separate the act of reshaping your data structures from the act of modifying the data itself. And there are two special methods, "copy" and "sever", that help you control the data flow connection between related variables. Watch this:
$y = $x->slice("1:3"); # Slice maintains a link between $x and $y.
$y += 5; # $x is changed!
See how $x got changed?? That's the link doing its thing. If you want to force a physical copy and no data flow at all, you copy or sever the slice expression:
$y = $x->slice("1:3")->copy;
$y += 5; # $x is not changed.
$y = $x->slice("1:3")->sever;
$y += 5; # $x is not changed.
The difference between "sever" and "copy" is that sever acts on (and returns) its argument, while copy produces a disconnected copy. So if you say:
$y = $x->slice("1:3");
$c = $y->sever;
then the variables $y and $c point to the same object β but with ->copy they would not. sneaky little distinction, remember it π
π₯ FUNCTIONS π₯
πΈ index πΈ
Signature: (a(n); indx ind(); [oca] c())
"index", "index1d", and "index2d" provide rudimentary index indirection. Like:
$c = index($source,$ind);
$c = index1d($source,$ind);
$c = index2d($source2,$ind1,$ind2);
They use the $ind variables as indices to look up values in $source. The three routines broadcast slightly differently, so let's get into it:
"index"uses direct broadcasting for 1-D indexing across the 0 dim of$source. It can broadcast over source broadcast dims or index broadcast dims, but not (easily) both: If$sourcehas more than 1 dimension and$indhas more than 0 dimensions, they must agree in a broadcasting sense."index1d"uses a single active dim in$indto produce a list of indexed values in the 0 dim of the output β it's useful for collapsing$sourceby indexing with a single row of values along$source's 0 dimension. The output has the same number of dims as$source. The 0 dim of the output has size 1 if$indis a scalar, and the same size as the 0 dim of$indif it is not. If$indand$sourceboth have more than 1 dim, then all dims higher than 0 must agree in a broadcasting sense."index2d"works like"index"but uses separate ndarrays for X and Y coordinates. For more general N-dimensional indexing, see thePDL::NiceSlicesyntax orPDL::Slices(in particular"slice","indexND", and"range").
These functions are two-way, i.e. after
$c = $x->index(pdl[0,5,8]);
$c .= pdl [0,2,4];
the changes in $c will flow back to $x. π«
"index" provides simple broadcasting: multiple-dimensioned arrays are treated as collections of 1-D arrays, so that
$x = xvals(10,10)+10*yvals(10,10);
$y = $x->index(3);
$c = $x->index(9-xvals(10));
puts a single column from $x into $y, and puts a single element from each column of $x into $c. If you want to extract multiple columns from an array in one operation, see "dice" or "indexND".
π¨ index barfs if any of the index values are bad. that's not a drill.
π§ index1d π§
Signature: (a(n); indx ind(m); [oca] c(m))
"index", "index1d", and "index2d" provide rudimentary index indirection, and they all work like this:
$c = index($source,$ind);
$c = index1d($source,$ind);
$c = index2d($source2,$ind1,$ind2);
They take the $ind variables and use them as indices to look up values in $source. The three routines broadcast a little differently from each other:
"index"does direct broadcasting for 1-D indexing across the 0 dim of$source. It can broadcast over source broadcast dims or index broadcast dims, but not (easily) both: If$sourcehas more than 1 dimension and$indhas more than 0 dimensions, they must agree in a broadcasting sense."index1d"uses a single active dim in$indto produce a list of indexed values in the 0 dim of the output β it is useful for collapsing$sourceby indexing with a single row of values along$source's 0 dimension. The output has the same number of dims as$source. The 0 dim of the output has size 1 if$indis a scalar, and the same size as the 0 dim of$indif it is not. If$indand$sourceboth have more than 1 dim, then all dims higher than 0 must agree in a broadcasting sense."index2d"works like"index"but uses separate ndarrays for X and Y coordinates. For more general N-dimensional indexing, see thePDL::NiceSlicesyntax orPDL::Slices(in particular"slice","indexND", and"range").
These functions are two-way, i.e. after
$c = $x->index(pdl[0,5,8]);
$c .= pdl [0,2,4];
the changes in $c will flow back to $x. π«Ά
"index" provides simple broadcasting: multiple-dimensioned arrays are treated as collections of 1-D arrays, so that
$x = xvals(10,10)+10*yvals(10,10);
$y = $x->index(3);
$c = $x->index(9-xvals(10));
puts a single column from $x into $y, and puts a single element from each column of $x into $c. If you want to extract multiple columns from an array in one operation, see "dice" or "indexND".
β οΈ index1d propagates BAD index elements to the output variable. so if a bad index shows up, it's coming with you, fr fr.
π¦ index2d π¦
Signature: (a(na,nb); indx inda(); indx indb(); [oca] c())
"index", "index1d", and "index2d" provide rudimentary index indirection:
$c = index($source,$ind);
$c = index1d($source,$ind);
$c = index2d($source2,$ind1,$ind2);
They use the $ind variables as indices to look up values in $source. The three routines broadcast slightly differently, and here's the breakdown:
"index"uses direct broadcasting for 1-D indexing across the 0 dim of$source. It can broadcast over source broadcast dims or index broadcast dims, but not (easily) both: If$sourcehas more than 1 dimension and$indhas more than 0 dimensions, they must agree in a broadcasting sense."index1d"uses a single active dim in$indto produce a list of indexed values in the 0 dim of the output β it is useful for collapsing$sourceby indexing with a single row of values along$source's 0 dimension. The output has the same number of dims as$source. The 0 dim of the output has size 1 if$indis a scalar, and the same size as the 0 dim of$indif it is not. If$indand$sourceboth have more than 1 dim, then all dims higher than 0 must agree in a broadcasting sense."index2d"works like"index"but uses separate ndarrays for X and Y coordinates. For more general N-dimensional indexing, see thePDL::NiceSlicesyntax orPDL::Slices(in particular"slice","indexND", and"range").
These functions are two-way, i.e. after
$c = $x->index(pdl[0,5,8]);
$c .= pdl [0,2,4];
the changes in $c will flow back to $x. β
"index" provides simple broadcasting: multiple-dimensioned arrays are treated as collections of 1-D arrays, so that
$x = xvals(10,10)+10*yvals(10,10);
$y = $x->index(3);
$c = $x->index(9-xvals(10));
puts a single column from $x into $y, and puts a single element from each column of $x into $c. If you want to extract multiple columns from an array in one operation, see "dice" or "indexND".
π¨ index2d barfs if either of the index values are bad. two bad indices, zero survivors.
π indexNDb π
Backwards-compatibility alias for indexND. that's it, that's the whole section, she's just an alias π
π indexND π
Find selected elements in an N-D ndarray, with optional boundary handling.
$out = $source->indexND( $index, [$method] )
$source = 10*xvals(10,10) + yvals(10,10);
$index = pdl([[2,3],[4,5]],[[6,7],[8,9]]);
print $source->indexND( $index );
[
[23 45]
[67 89]
]
IndexND collapses $index by lookup into $source. The 0th dimension of $index is treated as coordinates in $source, and the return value has the same dimensions as the rest of $index. The returned elements are looked up from $source. Dataflow works β propagated assignment flows back into $source. yes, two-way again, she's consistent.
IndexND and IndexNDb were originally separate routines but they are both now implemented as a call to "range", and have identical syntax to one another.
SEE ALSO:
"whichND" in PDL::Primitive returns N-D indices into a multidimensional PDL, suitable for feeding to this. π
π rangeb π
Signature: (P(); C(); pdl *ind_pdl; SV *size_sv; SV *boundary_sv)
Engine for "range". Same calling convention as "range", but you must supply all parameters. "rangeb" is marginally faster as it makes a direct PP call, avoiding the perl argument-parsing step. speed queen behavior β‘
π range π
Extract selected chunks from a source ndarray, with boundary conditions.
$out = $source->range($index,[$size,[$boundary]])
Returns elements or rectangular slices of the original ndarray, indexed by the $index ndarray. $source is an N-dimensional ndarray, and $index is an ndarray whose first dimension has size up to N. Each row of $index is treated as coordinates of a single value or chunk from $source, specifying the location(s) to extract.
If you specify a single index location, then range is essentially an expensive slice, with controllable boundary conditions. (not me calling it expensive, the docs said it first π)
β¨ INPUTS β¨
$index and $size can be ndarrays or array refs such as you would feed to zeroes and its ilk. If $index's 0th dimension has size higher than the number of dimensions in $source, then $source is treated as though it had trivial dummy dimensions of size 1, up to the required size to be indexed by $index β so if your source array is 1-D and your index array is a list of 3-vectors, you get two dummy dimensions of size 1 on the end of your source array. dummy dims, the humble MVPs.
You can extract single elements or N-D rectangular ranges from $source, by setting $size. If $size is undef or zero, then you get a single sample for each row of $index. This behavior is similar to "indexNDb", which is in fact implemented as a call to "range".
If $size is positive then you get a range of values from $source at each location, and the output has extra dimensions allocated for them. $size can be a scalar, in which case it applies to all dimensions, or an N-vector, in which case each element is applied independently to the corresponding dimension in $source. See below for details. π
$boundary is a number, a string, or a list ref, and it tells range what to do when your ranges yeet themselves right off the edge of $source. Don't specify anything? Then bestie, the default is forbid boundary violations on all axes β no edge-crossing allowed, periodt. Give it exactly <em>one</em> boundary condition and that one rule applies to <em>every</em> axis. Give it more (as elements of a list ref, or as a packed string β scroll down, we get there) and they get applied to the dimensions in the order they show up, with the last one stretching out to cover all the subsequent dimensions. (It sounds cursed, it's really not. See the examples below, I promise it clicks π
)
0(synonyms:'f','forbid') (default) β ranges are not allowed to cross the boundary of the original PDL, no cap. Disallowed ranges throw an error. And the drama? Those errors get thrown at evaluation time, not at the time of the range call β same behavior as"slice".1(synonyms:'t','truncate') β values that land outside the original ndarray get BAD if you've got bad value support compiled into your PDL <em>and</em> you set the badflag for the source PDL; or 0 if you haven't (so heads up: you <em>must</em> set the badflag if you want BADs for out-of-bound values, otherwise you just get 0, fr fr). Reverse dataflow works OK for the portion of the child that's in-bounds. The out-of-bounds part of the child gets reset to(BAD|0)during each dataflow operation, but execution continues β we don't stop the show. π«Ά2(synonyms:'e','x','extend') β values that would be outside the original ndarray point instead to the nearest allowed value within the ndarray. See the CAVEAT below about mappings that are not single valued, bestie.3(synonyms:'p','periodic') β periodic boundary conditions apply: the numbers in$indexare applied, strict-modulo the corresponding dimensions of$source. This is literally equivalent to duplicating the$sourcendarray throughout N-D space. See the CAVEAT below about mappings that are not single valued.4(synonyms:'m','mirror') β mirror-reflection periodic boundary conditions apply. See the CAVEAT below about mappings that are not single valued.
All the boundary condition identifiers start with unique characters, which is the whole reason you can feed in multiple boundary conditions as either a list ref or a packed string. (The packed string is marginally faster to run, if you're optimizing β¨) For example, the four expressions [0,1], ['forbid','truncate'], ['f','t'], and 'ft' <em>all</em> specify that violating the boundary in the 0th dimension throws an error, and all other dimensions get truncated. Same energy, four different outfits. π
If you feed in a single string, it's interpreted as a packed boundary array <em>if</em> all of its characters are valid boundary specifiers (e.g. 'pet'), but as a single word-style specifier if they are not (e.g. 'forbid').
β¨ OUTPUT β¨
The output broadcasts over <em>both</em> $index and $source. Because implicit broadcasting can happen in a couple of ways, a little thought is needed here β don't skip this part. The returned dimension list is stacked up like this:
(index broadcast dims), (index dims (size)), (source broadcast dims)
The first few dims of the output correspond to the extra dims of $index (beyond the 0 dim). They let you pick out individual ranges from a large, broadcasted collection. So satisfying. π
The middle few dims of the output correspond to the size dims specified in $size, and they contain the range of values that gets extracted at each location in $source. Every nonzero element of $size is copied to the dimension list right here, so that if you feed in (for example) "$size = [2,0,1]" you get an index dim list of "(2,1)".
The last few dims of the output correspond to extra dims of $source beyond the number of dims indexed by $index. These dims act like ordinary broadcast dims, because adding more dims to $source just tacks extra dims on the end of the output. Each source broadcast dim ranges over the entire corresponding dim of $source.
Dataflow: Dataflow is bidirectional.
Examples: here are basic examples of the "range" operation, showing how to get ranges out of a small matrix. The first few examples show extraction and selection of individual chunks. The last example shows how to mark loci in the original matrix (using dataflow). Iconic behavior honestly.
pdl> $src = 10*xvals(10,5)+yvals(10,5)
pdl> print $src->range([2,3]) # Cut out a single element
23
pdl> print $src->range([2,3],1) # Cut out a single 1x1 block
[
[23]
]
pdl> print $src->range([2,3], [2,1]) # Cut a 2x1 chunk
[
[23 33]
]
pdl> print $src->range([[2,3]],[2,1]) # Trivial list of 1 chunk
[
[
[23]
[33]
]
]
pdl> print $src->range([[2,3],[0,1]], [2,1]) # two 2x1 chunks
[
[
[23 1]
[33 11]
]
]
pdl> # A 2x2 collection of 2x1 chunks
pdl> print $src->range([[[1,1],[2,2]],[[2,3],[0,1]]],[2,1])
[
[
[
[11 22]
[23 1]
]
[
[21 32]
[33 11]
]
]
]
pdl> $src = xvals(5,3)*10+yvals(5,3)
pdl> print $src->range(3,1) # Broadcast over y dimension in $src
[
[30]
[31]
[32]
]
pdl> $src = zeroes(5,4);
pdl> $src->range(pdl([2,3],[0,1]),pdl(2,1)) .= xvals(2,2,1) + 1
pdl> print $src
[
[0 0 0 0 0]
[2 2 0 0 0]
[0 0 0 0 0]
[0 0 1 1 0]
]
β οΈ CAVEAT β οΈ
It's quite possible to select multiple ranges that intersect. And in that case, modifying the ranges doesn't have a guaranteed result in the original PDL β the result is just an arbitrary choice among the valid values. For some things that's OK; but for others it's absolutely not. In particular, this doesn't work:
pdl> $photon_list = PDL::RandVar->new->sample(500)->reshape(2,250)*10
pdl> $histogram = zeroes(10,10)
pdl> $histogram->range($photon_list,1)++; #not what you wanted
The reason is that if two photons land in the same bin, then that bin doesn't get incremented twice. (That may get fixed in a later version... the devs said what they said π)
π¦ PERMISSIVE RANGING π¦
If $index has too many dimensions compared to $source, then $source is treated as though it had dummy dimensions of size 1, up to the required number of dimensions. These virtual dummy dimensions have the usual boundary conditions applied to them. Cute, right?
If the 0 dimension of $index is <em>ludicrously</em> large (if its size is more than 5 greater than the number of dims in the source PDL) then range will insist that you specify a size in every dimension, to make sure that you know what you're doing. Bestie, this is a guard rail, not a punishment. It catches a common error with range usage: confusing the initial dim (which is usually small) with another index dim (perhaps of size 1000). π
If the index variable is Empty, then range() always returns the Empty PDL. If the index variable is <em>not</em> Empty, indexing it always yields a boundary violation. All non-barfing conditions are treated as truncation, since there are no actual data to return.
π₯ EFFICIENCY π₯
Because "range" isn't an affine transformation (it involves lookup into a list of N-D indices), it is somewhat memory-inefficient for long lists of ranges, and keeping dataflow open is much slower than for affine transformations (which don't have to copy data around). Not the vibe for hot loops, but it is what it is.
Doing operations on small subfields of a large range is inefficient because the engine must flow the entire range back into the original PDL with every atomic perl operation, even if you only touch a single element. One way to speed up such code is to sever your range, so that PDL doesn't have to copy the data with each operation, then copy the elements explicitly at the end of your loop. Here's an example that labels each region in a range sequentially, using many small operations rather than a single xvals assignment:
### How to make a collection of small ops run fast with range...
$x = $data->range($index, $sizes, $bound)->sever;
$aa = $data->range($index, $sizes, $bound);
$x($_ - 1) .= $_ for 1..$x->nelem; # Lots of little ops
$aa .= $x;
"range" is a perl front-end to a PP function, "rangeb". Calling "rangeb" is marginally faster but requires that you include all arguments. No skipping the homework if you take the fast lane. β¨
π§ DEVEL NOTES π§
- index broadcast dimensions are effectively clumped internally. This makes it easier to loop over the index array but a little more brain-bending to tease out the algorithm.
rangeb processes bad values. It will set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
πΈ rle... wait no, rld πΈ
Signature: (indx a(n); b(n); [o]c(m))
Run-length decode a vector
Given a vector $x of the numbers of instances of values $y, run-length decode to $c. It's the un-packing of the packing, the plot twist reverse.
rld($x,$y,$c=null);
rld does not process bad values. It <em>will</em> set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
π rle π
Signature: (c(n); indx [o]a(m); [o]b(m))
Run-length encode a vector
Given vector $c, generate a vector $x with the number of each element, and a vector $y of the unique values. New in PDL 2.017, only the elements up to the first instance of 0 in $x are returned, which makes the common use case of a 1-dimensional $c simpler. For broadcast operation, $x and $y will be large enough to hold the largest row of $y, and only the elements up to the first instance of 0 in each row of $x should be considered.
$c = floor(4*random(10));
rle($c,$x=null,$y=null);
#or
($x,$y) = rle($c);
#for $c of shape [10, 4]:
$c = floor(4*random(10,4));
($x,$y) = rle($c);
#to see the results of each row one at a time:
foreach (0..$c->dim(1)-1){
my ($as,$bs) = ($x(:,($_)),$y(:,($_)));
my ($ta,$tb) = where($as,$bs,$as!=0); #only the non-zero elements of $x
print $c(:,($_)) . " rle==> " , ($ta,$tb) , "\trld==> " . rld($ta,$tb) . "\n";
}
# the inverse of (chance of all 6 3d6 rolls being >= each possible sum)
($nrolls, $ndice, $dmax) = (6, 3, 6);
($x, $x1) = (allaxisvals(($dmax) x $ndice)+1)->sumover->flat->qsort->rle;
$y = $x->cumusumover;
$yprob1x = $y->slice('-1:0')->double / $y->slice('(-1)');
$z = cat($x1, 1 / $yprob1x**$nrolls)->transpose;
rle does not process bad values. It <em>will</em> set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
π«Ά rlevec π«Ά
Signature: (c(M,N); indx [o]a(N); [o]b(M,N))
Run-length encode a set of vectors.
Higher-order rle(), for use with qsortvec(). The glow-up version, basically.
Given set of vectors $c, generate a vector $a with the number of occurrences of each element (where an "element" is a vector of length $M occurring in $c), and a set of vectors $b containing the unique values. As for rle(), only the elements up to the first instance of 0 in $a should be considered.
Can be used together with clump() to run-length encode "values" of arbitrary dimensions. Can be used together with rotate(), cat(), append(), and qsortvec() to count N-grams over a 1d PDL. N-grams? In <em>my</em> PDL? It's more likely than you think. π
See also: rle, qsortvec in PDL::Ufunc, uniqvec in PDL::Primitive. Contributed by Bryan Jurish <moocow@cpan.org>.
rlevec does not process bad values π
ββοΈ. It will set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
π« rldvec
Signature: (indx a(N); b(M,N); [o]c(M,N))
Run-length decode a set of vectors β it's giving higher-order rld(), bestie β¨
So like: you hand it a vector $a() that says how many times each row shows up, and a set $c() of row-vectors each of length $M, and it run-length decodes everything into $c(). Iconic behavior honestly.
You can absolutely pair it up with clump() to run-length decode "values" of whatever arbitrary dimensions you're working with π¦.
See also: rld. Contributed by Bryan Jurish <moocow@cpan.org>.
rldvec does not process bad values π. It will set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
π§ rleseq
Signature: (c(N); indx [o]a(N); [o]b(N))
Run-length encode a vector of subsequences π
Give it a vector $c() of concatenated, variable-length, variable-offset subsequences, and it generates a vector $a holding the length of each subsequence plus a vector $b holding the subsequence offsets. Same energy as rle(): only the elements up to the first instance of 0 in $a should even be considered, so don't come for the rest π
See also rle. Contributed by Bryan Jurish <moocow@cpan.org>.
rleseq does not process bad values π
. It will set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
πΈ rldseq
Signature: (indx a(N); b(N); [o]c(M))
Run-length decode a subsequence vector, no cap π§’
Give it a vector $a() of sequence lengths and a vector $b() of the corresponding offsets, and it decodes the concatenation of subsequences into $c(), exactly as if you'd written:
$c = null;
$c = $c->append($b($_)+sequence($a->type,$a($_))) foreach (0..($N-1));
See also: rld. Contributed by Bryan Jurish <moocow@cpan.org>.
rldseq does not process bad values π. It will set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
π rleND
Signature: (data(@vdims,N); int [o]counts(N); [o]elts(@vdims,N))
Run-length encode a set of (sorted) n-dimensional values β¨
It's the generalization arc of rle() and vv_rlevec(): give it a set of values $data, and you get back a vector $counts with the number of occurrences of each element (and an "element" here means a matrix of dimensions @vdims that shows up as a sequential run over the final dimension in $data), plus a set of vectors $elts containing the elements which begin a run. Really it's just a wrapper for clump() and rlevec(). Lowkey elegant π«Ά
See also: rle, rlevec. Contributed by Bryan Jurish <moocow@cpan.org>.
π rldND
Signature: (int counts(N); elts(@vdims,N); [o]data(@vdims,N);)
Run-length decode a set of (sorted) n-dimensional values, the sequel π¦
Generalization of rld() and rldvec(): give it a vector $counts() of the number of occurrences of each @vdims-dimensioned element, plus a set $elts() of @vdims-dimensioned elements, and it run-length decodes into $data().
Really just a wrapper for clump() and rldvec().
See also: rld, rldvec. Contributed by Bryan Jurish <moocow@cpan.org>.
π₯ xchg
Signature: (P(); C(); PDL_Indx n1; PDL_Indx n2)
exchange two dimensions π
Negative dimension indices count from the end, bestie.
The command
$y = $x->xchg(2,3);
creates $y to be like $x except that the dimensions 2 and 3 are exchanged with each other, i.e.
$y->at(5,3,2,8) == $x->at(5,3,8,2)
xchg does not process bad values π
ββοΈ. It will set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
π reorder
Re-orders the dimensions of a PDL based on the supplied list π
Similar to the xchg method, this method re-orders the dimensions of a PDL. But here's the glow-up: while xchg swaps the position of just two dimensions, reorder can change the positions of many dimensions all at once. Main character energy fr fr.
# Completely reverse the dimension order of a 6-Dim array.
$reOrderedPDL = $pdl->reorder(5,4,3,2,1,0);
The argument to reorder is an array representing where the current dimensions should go in the new array. In the above usage, the argument to reorder (5,4,3,2,1,0) indicates that the old dimensions ($pdl's dims) should be re-arranged to make the new pdl ($reOrderPDL) according to the following:
- Old position
5β new position0 - Old position
4β new position1 - Old position
3β new position2 - Old position
2β new position3 - Old position
1β new position4 - Old position
0β new position5
You do not need to specify all dimensions, only a complete set starting at position 0. (Extra dimensions are left where they are). This means, for example, that you can reorder() the X and Y dimensions of an image, and not care whether it is an RGB image with a third dimension running across color plane. So chill π
Example:
pdl> $x = sequence(5,3,2); # Create a 3-d Array
pdl> p $x
[
[
[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
]
[
[15 16 17 18 19]
[20 21 22 23 24]
[25 26 27 28 29]
]
]
pdl> p $x->reorder(2,1,0); # Reverse the order of the 3-D PDL
[
[
[ 0 15]
[ 5 20]
[10 25]
]
[
[ 1 16]
[ 6 21]
[11 26]
]
[
[ 2 17]
[ 7 22]
[12 27]
]
[
[ 3 18]
[ 8 23]
[13 28]
]
[
[ 4 19]
[ 9 24]
[14 29]
]
]
The above is a simple example that could be duplicated by calling $x->xchg(0,2), but it demonstrates the basic functionality of reorder. Ate, honestly π
Heads up bestie: as this is an index function, any modifications to the result PDL will change the parent. It's giving "shares the vibe" not "copies the vibe" π
π mv
Signature: (P(); C(); PDL_Indx n1; PDL_Indx n2)
move a dimension to another position π
The command
$y = $x->mv(4,1);
creates $y to be like $x except that the dimension 4 is moved to the place 1, so:
$y->at(1,2,3,4,5,6) == $x->at(1,5,2,3,4,6);
The other dimensions are moved accordingly β they all shuffle down the line like a group chat when someone leaves β¨ Negative dimension indices count from the end.
mv does not process bad values π. It will set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
β using
Returns array of column numbers requested π
line $pdl->using(1,2);
Plot, as a line, column 1 of $pdl vs. column 2:
pdl> $pdl = rcols("file");
pdl> line $pdl->using(1,2);
π¦ diagonal
Signature: (P(); C(); PDL_Indx whichdims[])
Returns the multidimensional diagonal over the specified dimensions β¨
The diagonal is placed at the first (by number) dimension that is diagonalized. The other diagonalized dimensions are removed. So if $x has dimensions (5,3,5,4,6,5) then after
$d = $x->diagonal(dim1, dim2,...)
$y = $x->diagonal(0,2,5);
the ndarray $y has dimensions (5,3,4,6) and $y->at(2,1,0,1) refers to $x->at(2,1,2,0,1,2).
NOTE: diagonal doesn't handle broadcastids correctly. XXX FIX π¬ not me pointing at the bug like π
pdl> $x = zeroes(3,3,3);
pdl> ($y = $x->diagonal(0,1))++;
pdl> p $x
[
[
[1 0 0]
[0 1 0]
[0 0 1]
]
[
[1 0 0]
[0 1 0]
[0 0 1]
]
[
[1 0 0]
[0 1 0]
[0 0 1]
]
]
diagonal does not process bad values π. It will set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
π lags
Signature: (P(); C(); PDL_Indx nthdim;PDL_Indx step;PDL_Indx n)
Returns an ndarray of lags to parent π
Usage:
$lags = $x->lags($nthdim,$step,$nlags);
I.e. if $x contains
[0,1,2,3,4,5,6,7]
then
$y = $x->lags(0,2,2);
is a (5,2) matrix
[2,3,4,5,6,7]
[0,1,2,3,4,5]
This order of returned indices is kept because the function is called lags i.e. the nth lag is n steps behind the original. It's literally in the name bestie π«Ά
$step and $nlags must be positive. $nthdim can be negative and will then be counted from the last dim backwards in the usual way (-1 = last dim).
lags does not process bad values π
. It will set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
π§ splitdim
Signature: (P(); C(); PDL_Indx nthdim;PDL_Indx nsp)
Splits a dimension in the parent ndarray (opposite of clump) π₯ As of 2.076, throws exception if non-divisible nsp given, and can give negative nthdim which then counts backwards. So no sneaky half-splits, it will yell at you π€
After
$y = $x->splitdim(2,3);
the expression
$y->at(6,4,m,n,3,6) == $x->at(6,4,m+3*n)
is always true (m has to be less than 3).
splitdim does not process bad values π. It will set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
π« rotate
Signature: (x(n); indx shift(); [oca]y(n))
Shift vector elements along with wrap. Flows data back&forth π
rotate does not process bad values π. It will set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
π broadcastI
Signature: (P(); C(); PDL_Indx id; PDL_Indx whichdims[])
internal π€«
Put some dimensions to a broadcastid.
$y = $x->broadcastI(0,1,5); # broadcast over dims 1,5 in id 1
broadcastI does not process bad values π
. It will set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
π unbroadcast
Signature: (P(); C(); PDL_Indx atind)
All broadcasted dimensions are made real again β¨
See [TBD Doc] for details and examples. (Yes bestie, the docs are literally a to-be-determined, we move π« )
unbroadcast does not process bad values π. It will set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays.
π² dice
Dice rows/columns/planes out of a PDL using indexes for each dimension π²
This function can be used to extract irregular subsets along many dimension of a PDL, e.g. only certain rows in an image, or planes in a cube. This can of course be done with the usual dimension tricks but this saves having to figure it out each time! No cap, it's a lifesaver π«Ά
This method is similar in functionality to the slice method, but slice requires that contiguous ranges or ranges with constant offset be extracted. (i.e. slice requires ranges of the form 1,2,3,4,5 or 2,4,6,8,10). Because of this restriction, slice is more memory efficient and slightly faster than dice. So yeah, slice is the frugal one, dice is the flexible one π
$slice = $data->dice([0,2,6],[2,1,6]); # Dicing a 2-D array
The arguments to dice are arrays (or 1D PDLs) for each dimension in the PDL. These arrays are used as indexes to which rows/columns/cubes,etc to dice-out (or extract) from the $data PDL.
Use X to select all indices along a given dimension (compare also mslice). As usual (in slicing methods) trailing dimensions can be omitted implying X'es for those. Periodt β¨
pdl> $x = sequence(10,4)
pdl> p $x
[
[ 0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]
]
pdl> p $x->dice([1,2],[0,3]) # Select columns 1,2 and rows 0,3
[
[ 1 2]
[31 32]
]
pdl> p $x->dice(X,[0,3])
[
[ 0 1 2 3 4 5 6 7 8 9]
[30 31 32 33 34 35 36 37 38 39]
]
pdl> p $x->dice([0,2,5])
[
[ 0 2 5]
[10 12 15]
[20 22 25]
[30 32 35]
]
okay so real talk π
β this is an index function, which means any modifications you make to the slice will change the parent too. Yes, the slice is lowkey still holding hands with its source. If you want to write back through it, that's what the .= operator is for. π«Ά
π² dice_axis π²
Dice rows/columns/planes from a single PDL axis (dimension) using index along a specified axis. β¨
Bestie, this one is so handy. It lets you pull out irregular subsets along any dimension β like only certain rows in an image, or certain planes in a cube. Sure, you could do it with the usual dimension tricks, but this saves you from having to figure that out every single time. No cap. π§
$slice = $data->dice_axis($axis,$index);
pdl> $x = sequence(10,4)
pdl> $idx = pdl(1,2)
pdl> p $x->dice_axis(0,$idx) # Select columns
[
[ 1 2]
[11 12]
[21 22]
[31 32]
]
pdl> $t = $x->dice_axis(1,$idx) # Select rows
pdl> $t.=0
pdl> p $x
[
[ 0 1 2 3 4 5 6 7 8 9]
[ 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0]
[30 31 32 33 34 35 36 37 38 39]
]
The trick to using this β and this is the whole vibe check β is that the index selects elements along the dimensions specified. So if you've got a 2D image, axis=0 will select certain "X" values, i.e. it extracts columns. πΈ
And again, because this is an index function, any modifications to the slice will change the parent. She's connected, she's committed, she's not letting go. π
π slice π
Signature: (P(); C(); pdl_slice_args *arglist)
$slice = $data->slice([2,3],'x',[2,2,0],"-1:1:-1", "*3");
Okay so this is THE one. slice extracts rectangular slices of an ndarray β from a string specifier, an array ref specifier, or a mix of both. π
slice is the main method for extracting regions of PDLs and manipulating their dimensionality. You can call it directly, or you can get it via the NiceSlice source prefilter that extends Perl syntax to include array slicing. Both are iconic. β¨
What can it do, you ask? slice can extract regions along each dimension of a source PDL, subsample or reverse those regions, dice each dimension by selecting a list of locations along it, or do basic PDL indexing. And here's the slay part: the selected subfield stays connected to the original PDL via dataflow. In most cases this neither allocates more memory nor slows down subsequent operations on either of the two connected PDLs. It's giving efficiency. π¦
So you pass in a list of arguments. Each term in the list controls the disposition of one axis of the source PDL and/or the returned PDL. Each term can be:
- a string-format cut specifier,
- a list ref that gives the same info without any string manipulation drama, or
- a PDL with up to 1 dimension giving the indices along that axis that should be selected.
And if you want to pass in a single string specifier for the entire operation, you can just pass in a comma-delimited list as the first argument. slice detects that condition and splits the string into a regular argument list. This calling style is fully backwards compatible with slice calls from before PDL 2.006. Respect the elders. π
π§΅ STRING SYNTAX π§΅
If a particular argument to slice is a string, it gets parsed as a selection, an affine slice, or a dummy dimension, depending on the form. Leading or trailing whitespace in any part of each specifier is ignored (though it is not ignored within numbers, so don't get cute). π
'',:, orX-- keep β The empty string,:, orXcause the entire corresponding dimension to be kept unchanged. She stays exactly as she is. β¨<n>-- selection β A single number alone causes a single index to be selected from the corresponding dimension. The dimension is kept (and reduced to size 1) in the output.(<n>)-- selection and collapse β A single number in parenthesis causes a single index to be selected from the corresponding dimension. The dimension is discarded β completely eliminated β in the output. Gone. Not coming back. π<n>:<m>-- select an inclusive range β Two numbers separated by a colon select a range of values from the corresponding axis, e.g.3:4selects elements 3 and 4 along the corresponding axis, and reduces that axis to size 2 in the output. Both numbers are regularized so that you can address the last element of the axis with an index of-1. If, after regularization, the two numbers are the same, then exactly one element gets selected (just like the<n>case). If, after regularization, the second number is lower than the first, then the resulting slice counts down rather than up β e.g.-1:0will return the entire axis, in reversed order. Iconic behaviour fr fr. π₯<n>:<m>:<s>-- select a range with explicit step β If you include a third parameter, it's the stride of the extracted range. For example,0:-1:2will sample every other element across the complete dimension. Specifying a stride of1prevents autoreversal β so to ensure that your slice is always forward you can specify, e.g.,2:$n:1. In that case, an "impossible" slice gets an Empty PDL (with 0 elements along the corresponding dimension), so you can generate an Empty PDL with a slice of the form2:1:1. Math girlies rise. π*<n>-- insert a dummy dimension β Dummy dimensions aren't present in the original source and are "mocked up" to match dimensional slots, by repeating the data in the original PDL some number of times. An asterisk followed by a number produces a dummy dimension in the output β for example*2will generate a dimension of size 2 at the corresponding location in the output dim list. Omitting the number (and using just an asterisk) inserts a dummy dimension of size 1. π
π§Ί ARRAY REF SYNTAX π§Ί
If you feed in an ARRAY ref as a slice term, then it can have 0-3 elements. The first element is the start of the slice along the corresponding dim; the second is the end; and the third is the stepsize. Different combinations of inputs give you the same flexibility as the string syntax. Same energy, different outfit. π
[]- keep dim intact β An empty ARRAY ref keeps the entire corresponding dim.[ 'X' ]- keep dim intact[ '*',$n ]- generate a dummy dim of size $n β If$nis missing, you get a dummy dim of size 1.[ $dex, , 0 ]- collapse and discard dim β$dexmust be a single value. It is used to index the source, and the corresponding dimension is discarded.[ $start, $end ]- collect inclusive slice β In the simple two-number case, you get a slice that runs up or down (as appropriate) to connect$startand$end.[ $start, $end, $inc ]- collect inclusive slice β The three-number case works exactly like the three-number string case above.
π― PDL args for dicing π―
If you pass in a 0- or 1-D PDL as a slicing argument, the corresponding dimension is "diced" β you get one position along the corresponding dim, per element of the indexing PDL. For example, $x->slice( pdl(3,4,9)) gives you elements 3, 4, and 9 along the 0 dim of $x. π²
Heads up bestie: because dicing is not an affine transformation, it is slower than direct slicing β even though the syntax is super convenient. Worth knowing, iykyk. π
$x->slice('1:3'); # return the second to fourth elements of $x
$x->slice('3:1'); # reverse the above
$x->slice('-2:1'); # return last-but-one to second elements of $x
$x->slice([1,3]); # Same as above three calls, but using array ref syntax
$x->slice([3,1]);
$x->slice([-2,1]);
One more thing: slice does not process bad values. It will set the bad-value flag of all output ndarrays if the flag is set for any of the input ndarrays. She's honest about it. πΈ
π BUGS π
- For the moment, you can't slice one of the zero-length dims of an empty ndarray. It is not clear how to implement this in a way that makes sense. It's giving "we tried and it got weird". π
- Many types of index errors are reported far from the indexing operation that caused them. This is caused by the underlying architecture:
slice()sets up a mapping between variables, but that mapping isn't tested for correctness until it is used (potentially much later). So the error shows up fashionably late. π
π AUTHOR π
Copyright (C) 1997 Tuomas J. Lukka. Contributions by Craig DeForest, deforest@boulder.swri.edu. Documentation contributions by David Mertens. All rights reserved. There is no warranty. You are allowed to redistribute this software / documentation under certain conditions. For details, see the file COPYING in the PDL distribution. If this file is separated from the PDL distribution, the copyright notice should be included in the file. π«Ά
And that's the whole page, bestie β you made it to the end! π This one shipped with perl v5.38.2, dated 2024-04-10, and she lives in the Slices(3pm) section of the manual. Now go slice something gorgeous. β¨ππ¦
π¦ where this page comes from
- ubuntu noble: pdl 1:2.085-1ubuntu1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms