10
$\begingroup$

Why does Count not work with SparseArrays, and is there any function I can use to replace it?

What would be an efficient workaround?

Example:

a=ConstantArray[0,{10},SparseArray];
Count[a,0] (* Returns 0, should be 10 *)
Count[Normal@a,0] (* returns 10, as it should *)

Implementation:

Edit: Is this implementation optimal?

sparseCount[array_, val_] :=
 Block[{nonBackgroundValues},
  nonBackgroundValues = array["NonzeroValues"];
  If[val == array["Background"],
   Length[array] + Count[nonBackgroundValues, val] - 
    Length[nonBackgroundValues],
   Count[nonBackgroundValues, val]
   ]
  ]
$\endgroup$
0
11
$\begingroup$

I can't speak to why exactly it doesn't work, but as for a workaround...

If you want to simply count how many values are unspecified, you can take the product of the dimensions (to get the hypothetical number of elements) and then subtract off the number of specifications (note that one specification is the default one, so we must subtract 1):

(* s is a SparseArray *)
(* Counting number of default values: *)
Times @@ Dimensions[s] - (Length[ArrayRules[s]] - 1)

If you want to count how many elements of a given form x that s has, but only among specified elements, you can use

(* Counting x's among non-default values: *)
Count[ Values[Most @ ArrayRules[s]], x]

Since SparseArrays always subsume specified values that happen to be the same as the default value into the default value (e.g. ArrayRules[SparseArray[{1 -> 5, 2 -> 0, 10 -> 256}]] gives {1 -> 5, 10 -> 256}), and since the last element of ArrayRules is always the default rule {_, _, ...} -> def, we can package this into a function which checks if the number we're counting is the default value of our sparse array or not, and then does one of the two above accordingly.

SparseCount[s_, x_] := Module[{values = Values[ArrayRules[s]]},
    If[MatchQ[Last[values],x],
       Times @@ Dimensions[s] - (Length[values] - 1),
       Count[values,x]
      ]]

(Note that we no longer need the Most, since if x were going to match the default value, it would have been caught by the If condition.)

Hope this helps!

$\endgroup$
3
  • 2
    $\begingroup$ I should note that there is a pitfall one should be aware of when manipulating SparseArray[] objects like this: it can happen that one or a few of the elements of the "NonzeroValues" property is the same as the background value, and those won't get counted by this process (cf. this previous thread). $\endgroup$
    – J. M.'s torpor
    May 6 '20 at 3:58
  • 1
    $\begingroup$ As an example: SparseCount[ SparseArray[{{3} -> 1, {6} -> 2}, 7] - SparseArray[{{3} -> 1, {6} -> 1}, 7], 0] will give the answer 5 instead of the correct answer 6. Preprocessing will cure this: SparseCount[SparseArray[SparseArray[{{3} -> 1, {6} -> 2}, 7] - SparseArray[{{3} -> 1, {6} -> 1}, 7]], 0]. $\endgroup$
    – J. M.'s torpor
    May 6 '20 at 3:58
  • $\begingroup$ Ah, gotcha, I considered that, but wrongly concluded from the preprocessed form that SparseArray objects always automatically deleted the background values from their rules! I'll fix the above once I find out which is more efficient: preprocessing each time or counting background values! $\endgroup$
    – thorimur
    May 7 '20 at 2:58
7
$\begingroup$
saCount[s_SparseArray, a_] := Block[{v = s["NonzeroValues"] }, 
  Count[v, a] +  If[a == s["Background"], Times @@ Dimensions[s] - Length@v, 0]]

saCount[a, 0]
 10

Timings:

sa = SparseArray[Table[{2^i, 3^i + i, i^5} -> 1, {i, 10}]]

enter image description here

saCount[sa, 0] // RepeatedTiming
 {6.5*10^-6, 6047641599990}
SparseCount[sa, 0] // RepeatedTiming (* from thorimur's answer *)
 {0.000018, 6047641599990}
sparseCount[sa, 0] // RepeatedTiming (* from OP *)
 {8.*10^-6, 1014}
$\endgroup$
2
  • $\begingroup$ Thanks a lot @kglr, do you have any idea where the factor of 2 difference in speed comes from? Perhaps it's related to the use of Module, which occasionally is slower? $\endgroup$
    – Daniel
    May 5 '20 at 9:32
  • 1
    $\begingroup$ @Daniel, I think the difference comes from ArrayRules vs property "NonzeroValues". Try, for example, Most[ArrayRules[sa]]; // RepeatedTiming and sa["NonzeroValues"]; // RepeatedTiming. $\endgroup$
    – kglr
    May 5 '20 at 9:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.