21
$\begingroup$

I would like to count the negative values of a list.

My approach was Count[data, -_] which doesn't work.

How can I tell Mathematica to count all numbers with a negative sign?

$\endgroup$

11 Answers 11

31
$\begingroup$

I assume that you have numeric values. A much more efficient way would be

-Total[UnitStep[data] - 1]]

or

Total[1-UnitStep[data]]

Note: While the second notation is certainly a bit more compact, it is about 35% slower than the double-minus notation. I have no idea why. On my system, it takes on average 0.22 sec vs 0.30 sec.

Compare timings between the faster UnitStep version and the pattern matching approach:

data = RandomReal[{-10, 10}, 10^7];

Timing[-Total[UnitStep[data] - 1]]
(* ==> {0.222, 5001715} *)

Timing[Count[data, _?Negative]]
(* ==> {6.734, 5001715} *)
$\endgroup$
4
  • 2
    $\begingroup$ Total[1 - UnitStep[data]] is more compact, no? $\endgroup$ Commented Aug 20, 2012 at 13:28
  • $\begingroup$ @J.M. Clever!!! I will edit according to your comment. However, it seams to make the code slightly slower. Any idea why? $\endgroup$
    – Thomas
    Commented Aug 20, 2012 at 13:29
  • $\begingroup$ I get the same timings on my machine. Did you notice that it turns around for shorter data? (Try 100 or so.) $\endgroup$
    – sebhofer
    Commented Aug 20, 2012 at 18:02
  • 10
    $\begingroup$ The faster version does two negations but both applied to numbers, while the slow version negates a whole list $\endgroup$
    – Rojo
    Commented Aug 21, 2012 at 3:51
25
$\begingroup$

Use _?Negative:

list = RandomInteger[{-9, 9}, 30]

Count[list, _?Negative]

Your pattern will match an object with an explicit negative sign:

Count[{a, -b, c, a, -a, a, -b}, -_]

3

You could combine the patterns to match either:

Count[{-1, -2, 3, 4, -a, b, -c}, -_ | _?Negative]

4


Since this has become a speed competition (which is fine by me), rather than the beginner's question I took it to be, here is my own variation, using Tr for fast packed array summing:

neg = Length@# - Tr@UnitStep@# &;

Timings:

SetAttributes[timeAvg, HoldFirst]

timeAvg[func_] := Do[
    If[# > 0.3, Return[#/5^i]] & @@ Timing@Do[func, {5^i}],
    {i, 0, 15}
  ]

list = RandomInteger[1*^7 {-1, 1}, 1*^8];

-Total[UnitStep[list] - 1] // timeAvg

Length[list] - Total[UnitStep[list]] // timeAvg

neg @ list // timeAvg

0.592

0.234

0.1278

$\endgroup$
8
  • $\begingroup$ Perhaps you should mention that the latter solution only works for variables with an added minus, not for values. $\endgroup$ Commented Aug 20, 2012 at 11:49
  • $\begingroup$ Okay, so expanding on this question, are the easiest ways to find all values in the list less than, say, 5 Length[list] - Total[UnitStep[list - 5]] using the UnitStep method? Or is there another pattern match to use? $\endgroup$
    – kale
    Commented Aug 21, 2012 at 1:47
  • $\begingroup$ @kalewallace pattern matching methods are easier, more declarative and more general, but when everything is numeric, the other methods are way faster. Count[list, i_/;i<5] $\endgroup$
    – Rojo
    Commented Aug 21, 2012 at 3:54
  • 1
    $\begingroup$ @kalewallace you may find this answer of interest. But, as Rojo states there is a trade-off, with patterns being more general and often conceptually simpler, while numeric approaches are usually faster given the right data (packed arrays of machine size reals or integers). $\endgroup$
    – Mr.Wizard
    Commented Aug 21, 2012 at 9:03
  • 1
    $\begingroup$ @Anastasiia your comment: "this doesn't work if one wants to count complex numbers" doesn't make much sense as complex numbers are not simply positive or negative. Perhaps you just want Count[dat, _Complex] to count all complex numbers in dat, or Count[cr, _?(Im@# > 0 &)] to count all numbers with an imaginary part greater than zero? $\endgroup$
    – Mr.Wizard
    Commented Sep 19, 2012 at 15:24
19
$\begingroup$

For numeric values, the following:

Length[data] - Total[UnitStep[data]] 

is 50% faster than Thomas' solution.

Update: An even faster approach will be to compile to C, as shown in the following function f:

ClearAll[f];
f = Compile[{{vector, _Real, 1}, {bound, _Real}},
      Module[{t = 0, i = 1, len = Length[vector]}, 
        For[i = 1, i <= len, i++, t += Boole[vector[[i]] < bound]]; t], 
      CompilationTarget -> "C", "RuntimeOptions" -> "Speed"]

Using the timeAvg function, as defined in Mr. Wizard's answer, we have:

SetAttributes[timeAvg, HoldFirst]
timeAvg[func_] := 
  Do[If[# > 0.3, Return[#/5^i]] & @@ Timing@Do[func, {5^i}], {i, 0, 
    15}];

data = RandomReal[{-10, 10}, 10^7];

{-Total[UnitStep[data] - 1] // timeAvg,
 Length[data] - Total[UnitStep[data]] // timeAvg,
 Length[data] - Tr[UnitStep[data]] // timeAvg,
 f[data, 0.] // timeAvg}

(* ==> {0.105993, 0.0431972, 0.0422639, 0.0141324} *)

Note that the function f defined above will work for any upper bound, and not just 0. And would be significantly faster (at least in my machine) than the corresponding Length[data]- Tr[UnitStep[data - bound]] approach when bound is not zero.

For the dangers with the use of "RuntimeOptions"->"Speed", see the Mathematica help: RuntimeOptions.

$\endgroup$
7
$\begingroup$

This seems competitive in terms of speed:

Total@Clip[list, {0, 0}, {1, 0}]

Testing:

SetAttributes[timeAvg, HoldFirst]
$HistoryLength = 0;
timeAvg[func_] := 
 Do[If[# > 0.3, Return[#/5^i]] & @@ Timing@Do[func, {5^i}], {i, 0, 15}]
neg = Length@# - Tr@UnitStep@# &;
list = RandomReal[1*^7 {-1, 1}, 1*^7];

Total@Clip[list, {0, 0}, {1, 0}] // timeAvg
-Total[UnitStep[list] - 1] // timeAvg
Length[list] - Total[UnitStep[list]] // timeAvg
neg@list // timeAvg

(* 0.1594, 0.375, 0.1812, 0.197 *)
$\endgroup$
1
  • 1
    $\begingroup$ Interesting, although in my machine this new one is still significantly slower than the last two. $\endgroup$
    – ecoxlinux
    Commented Aug 22, 2012 at 17:37
6
$\begingroup$

I've found the fastest way to do this sort of thing is to use a very simple procedural Do loop compiled to C.

f2 = Compile[{{numbers, _Real, 1}}, 
  Block[{count = 0}, Do[If[number < 0., count++], {number, numbers}]; 
   count], CompilationTarget -> "C", "RuntimeOptions" -> "Speed"]
$\endgroup$
4
$\begingroup$

Lots of nice answers. Here's another, but I'm afraid it's quite slow.

Total[Select[data, # < 0 &]]

Though this totals all the negative numbers. To count them you could do:

-Total[Sign[Select[data, # < 0 &]]]

Then I got to thinking, "how would I program this in Matlab?"

 Total[1 - Sign[data]]/2
$\endgroup$
2
  • 1
    $\begingroup$ For the last one, this would be significantly faster: (Length[data] - Total[Sign[data]])/2, for the same reasons Rojo stated in Thomas' answer. $\endgroup$
    – ecoxlinux
    Commented Aug 22, 2012 at 17:27
  • $\begingroup$ good point -- thanks $\endgroup$
    – bill s
    Commented Aug 23, 2012 at 3:55
4
$\begingroup$

Another one:

Total@Boole@Negative@data
$\endgroup$
3
$\begingroup$

As a short variant

Count[data, x_ /; x < 0]
$\endgroup$
3
$\begingroup$

Using GroupBy

SeedRandom[0];
list = RandomInteger[{-9, 9}, 30];

Length @ GroupBy[list, Sign][-1]

16

Length @ GroupBy[list, Sign][#] & /@ {-1, 0, 1}

{16, 1, 13}

$\endgroup$
2
$\begingroup$

Using CountsBy:

list = {-9, 1, 1, -4, -9, -3, 1, -9, 4, 3, 4, -5, 8, -5, -2, -9};

CountsBy[list, Sign][-1]

(*9*)
$\endgroup$
1
$\begingroup$

Some more just because. I ma grabbing the list from @E. Chan-López

list = {-9, 1, 1, -4, -9, -3, 1, -9, 4, 3, 4, -5, 8, -5, -2, -9};

to begin with

Select[list, Negative] // Length

also

DeleteCases[list, x_ /; ! MatchQ[x, _?Negative]] // Length

furthermore

Pick[list, Negative[list]] // Length

and finally

Pick[#, 1 - HeavisideTheta@#, 1] &@list // Length
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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