I am performing a length-frequency analysis (read: histogram) of data in fishes. This is a common way in which fish biologists analyze life history data in fishes. An example of the data is as follows.

fish_lengths <- c(157, 163, 181, 181, 181, 181, 187, 187, 187, 187, 
    187, 187, 187, 187, 187, 187, 193, 199, 199, 199, 
    199, 199, 199, 199, 199, 199, 199, 199, 205, 205, 205, 205, 205, 
    205, 205, 205, 205, 205, 205, 205, 205, 211, 211, 211, 211, 211, 
    211, 211, 217, 217, 217, 223, 223, 223, 229,  229, 235, 241, 247, 
    247, 253, 253, 253, 259, 146, 169, 175, 175, 175, 175, 175, 181, 
    181, 181, 181, 181, 181, 181, 181, 181, 181, 187, 187, 187, 187, 
    187, 187, 187, 187, 187, 187, 193, 193, 193, 193, 193, 193, 193, 
    193, 193, 193, 193, 199, 199, 199, 199, 199, 199, 199, 199, 199, 
    199, 199, 199, 199, 205, 205, 205, 205, 205, 205, 205, 211, 211, 
    211, 217, 217, 217, 217, 223, 223, 223, 229, 229, 241, 247, 247, 
    253, 253, 253, 259)
hist(fish_lengths, main="Histogram of Fish Lengths", xlab="Length")

enter image description here

A major feature I've noticed in the data is that the fish in general just show up in the study area at a certain size, which is probably due to juvenile fishes migrating out of their spawning grounds once they reach a certain threshold of size. In the above histogram this threshhold is at about ~180. I am trying to figure out a way to explicitly calculate a value for this threshhold statistically, rather than arbitrarily guessing it based on the histogram.

The primary issue I am running into is I cannot simply define the size at which these fish leave the spawning grounds as the minimal size recorded in the dataset, as there are a few babies which wander out of the spawning grounds before they can survive on their own and thus get caught and recorded in the dataset (i.e., the few individuals recorded <170-180). Defining the threshold based on the bin widths can sometimes be deceiving if the individuals are really close to the dividing line for the next bin over (which has happened with some of my data.

That is, in the present dataset just looking at the data this value would seem to be about 175-181, given that is when the abundance of individual suddenly spikes, but how can I demonstrate this more rigorously?

Something similar that I have seen done in other analyses of fishes is calculating the median length at sexual maturity using a logistic analysis. That is, recording whether each fish is mature or immature and then calculating a median age-at-maturity using a logistic regression. However, this is something that requires data of individuals on both sizes of the critical threshold, it doesn't seem to have a way to calculate this value if you only have individuals on one side of the threshold (i.e., individuals at spawning grounds/in open water).

Is there any way to statistically model at what length this key shift happens given there seems to be a clear point at which individuals just show up in the study area (minus the few small stragglers)?

  • $\begingroup$ Do you have any information about the date of the measurements ? Could it be possible that the minimal size you are looking for is dependant of the season ? $\endgroup$ Feb 26, 2022 at 10:00
  • $\begingroup$ @MrSmithGoesToWashington Knowing the biology of the species, I think this is just a case where the lower entries are accidental. The species breeds in isolated areas and the babies stay there until they grow to a certain size, but a few wander out of the area early by accident and get caught by the sampling gear. $\endgroup$ Feb 26, 2022 at 20:39

1 Answer 1


Avoid histograms for this type of work, as they depend heavily on the choices of bin cutoffs. Density plots, which you can think of as smoothed histograms with each data point providing a separate bin, display the data continuously. With your data:

> library(ggplot2)
> ggplot(mapping=aes(x=fish_lengths)) + geom_density()

density plot of fish lengths

You can identify a length at which the density shows a locally large slope (although I'd be reluctant to use that as a sharp cutoff; it's usually best to model your data continuously). Approximate the slope of the density plot by the first differences between the density estimates.

> flDens <- density(fish_lengths)
> firstDiffs <- data.frame(x= (flDens$x[2:512]+ flDens$x[1:511])/2,y= flDens$y[2:512] - flDens$y[1:511])
> ggplot(firstDiffs,mapping=aes(x=x,y=y))+geom_line()+labs(y="First difference")

First differences of fish length density

For these data there's a pretty clear peak of the slope in your region of interest around values of 170-180. You can get the value at the peak by interrogating the first differences.

> firstDiffs$x[which.max(firstDiffs$y)]
[1] 177.7242

You could use a similar approach for dealing with the secondary peak at lengths above 240.

  • $\begingroup$ out of curiosity, is the first difference the same thing as the derivative? I looked it up and it doesn't seem entirely clear. $\endgroup$ Feb 26, 2022 at 21:26
  • $\begingroup$ @user2352714 it's proportional to the local linear slope when points are evenly spaced as they are here with the density() output. I plotted the difference in y values against the average x value for each two consecutive data points in the density() output. To get the local slope (approximation to the first derivative) you would divide the difference in y values by the corresponding difference in x values. $\endgroup$
    – EdM
    Feb 26, 2022 at 21:39

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.