1

I have three 2D arrays SandArray,ClayArray, and SiltArray. I also have a function described here. Below is my code, when I run the script I get a ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

def TextureClass(sand, clay, silt):
    
        #if sand + clay > 100 or sand < 0 or clay < 0:
        #    raise Exception('Inputs adds over 100% or are negative')

    if silt + 1.5*clay < 15:
        textural_class = 'sand'

    elif silt + 1.5*clay >= 15 and silt + 2*clay < 30:
        textural_class = 'loamy sand'

    elif (clay >= 7 and clay < 20 and sand > 52 and silt + 2*clay >= 30) or (clay < 7 and silt < 50 and silt + 2*clay >= 30):
        textural_class = 'sandy loam'

    elif clay >= 7 and clay < 27 and silt >= 28 and silt < 50 and sand <= 52:
        textural_class = 'loam'

    elif (silt >= 50 and clay >= 12 and clay < 27) or (silt >= 50 and silt < 80 and clay < 12):
        textural_class = 'silt loam'

    elif silt >= 80 and clay < 12:
        textural_class = 'silt'

    elif clay >= 20 and clay < 35 and silt < 28 and sand > 45:
        textural_class = 'sandy clay loam'

    elif clay >= 27 and clay < 40 and sand > 20 and sand <= 45:
        textural_class = 'clay loam'

    elif clay >= 27 and clay < 40 and sand <= 20:
        textural_class = 'silty clay loam'

    elif clay >= 35 and sand > 45:
        textural_class = 'sandy clay'

    elif clay >= 40 and silt >= 40:
        textural_class = 'silty clay'

    elif clay >= 40 and sand <= 45 and silt < 40:
        textural_class = 'clay'

    else:
        textural_class = 'na'

    return textural_class

Texture = TextureClass(SandArray,ClayArray,SiltArray)

Texture should be an array with the same shape as SandArray, ClayArray, and SiltArray but with the textural_class str as its values.

Is it possible to have an output array of text from a function having conditions and using arrays as its input arguments and if so, what am I missing?

Edit: Having tried texture = np.array(list(map(TextureClass,SandArray,ClayArray,SiltArray))) I still get the same ValueError

rweber
  • 132
  • 7
  • Does this answer your question? [ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()](https://stackoverflow.com/questions/10062954/valueerror-the-truth-value-of-an-array-with-more-than-one-element-is-ambiguous) – user19513069 Aug 08 '22 at 16:20
  • I understand why the error occurs. What I don't understand how to do is map the function to three arrays. – rweber Aug 08 '22 at 16:21

2 Answers2

1

I'm not really that knowledgeable in numpy, but, from what I searched in other questions, you could use vectorize to wrap your function.

Here's an example: How to apply a function / map values of each element in a 2d numpy array/matrix?


Using my previous approach of mostly built-in python-code:

You could zip the three arrays (either inside or outside your function, but I'd do it outside), then loop over the zipped arrays.

The zip() function returns a zip object, which is an iterator of tuples where the first item in each passed iterator is paired together, and then the second item in each passed iterator are paired together etc.

If the passed iterators have different lengths, the iterator with the least items decides the length of the new iterator.

Emphasis on the fact that it's assumed the three arrays have the same length. And, in this case, you'd need to bi-dimensionally zip:

So, instead of Texture = TextureClass(SandArray,ClayArray,SiltArray), you could use:

soilCompositions = (zip(sands, clays, silts) for sands, clays, silts in zip(SandArray, ClayArray, SiltArray))
Textures = ((TextureClass(sand, clay, silt) for sand, clay, silt in soilCompositionRow) for soilCompositionRow in soilCompositions)

Notice that I used generator comprehension, but you could just as easy use list comprehension instead:

soilCompositions = (zip(sands, clays, silts) for sands, clays, silts in zip(SandArray, ClayArray, SiltArray))
Textures = [[TextureClass(sand, clay, silt) for sand, clay, silt in soilCompositionRow] for soilCompositionRow in soilCompositions]
user19513069
  • 317
  • 1
  • 9
  • 1
    This should work for nxn arrays assuming that all three arrays are the same size, correct? All three arrays are 885 by 837. – rweber Aug 08 '22 at 16:44
  • Oohhh, it's bidimensional. This explains why your edit didn't work either. Yeah, I'll need to edit that. And no, in it's current form, this code will not work then. – user19513069 Aug 08 '22 at 16:50
  • I should have specified. Will add that to the original post. For context, I am attempting to apply this function to three raster data sets in ArcGIS. – rweber Aug 08 '22 at 16:52
  • Its throwing an `invalid syntax error` in this line of code `Textures = ((TextureClass(sand, clay, silt) for sand, clay, silt in soilCompositionRow) soilCompositionRow in soilCompositions)` at the second `soilCompositionRow`. – rweber Aug 08 '22 at 17:34
  • Oops, I forgot the second for. Also, apparently I've forgotten a letter in the second one – user19513069 Aug 08 '22 at 17:40
  • 1
    This was helpful and brought me on the right track. I had seen that thread you added to your comment but was weary as it seemed like `np.vectorize` would not be applicable to my function. – rweber Aug 08 '22 at 17:51
1

Applying a function to each element of a matrix requires np.vectorize. The documentation is available here. An example of a similar questions can be found here:How to apply a function / map values of each element in a 2d numpy array/matrix?.

I think this question is unique in that it shows the range of functions that np.vectorize works on. My original issue was whether or not np.vectorize would work for a conditional function like the one in my question.

def TextureClass(sand, clay, silt):
    
        #if sand + clay > 100 or sand < 0 or clay < 0:
        #    raise Exception('Inputs adds over 100% or are negative')

    if silt + 1.5*clay < 15:
        textural_class = 'sand'

    elif silt + 1.5*clay >= 15 and silt + 2*clay < 30:
        textural_class = 'loamy sand'

    elif (clay >= 7 and clay < 20 and sand > 52 and silt + 2*clay >= 30) or (clay < 7 and silt < 50 and silt + 2*clay >= 30):
        textural_class = 'sandy loam'

    elif clay >= 7 and clay < 27 and silt >= 28 and silt < 50 and sand <= 52:
        textural_class = 'loam'

    elif (silt >= 50 and clay >= 12 and clay < 27) or (silt >= 50 and silt < 80 and clay < 12):
        textural_class = 'silt loam'

    elif silt >= 80 and clay < 12:
        textural_class = 'silt'

    elif clay >= 20 and clay < 35 and silt < 28 and sand > 45:
        textural_class = 'sandy clay loam'

    elif clay >= 27 and clay < 40 and sand > 20 and sand <= 45:
        textural_class = 'clay loam'

    elif clay >= 27 and clay < 40 and sand <= 20:
        textural_class = 'silty clay loam'

    elif clay >= 35 and sand > 45:
        textural_class = 'sandy clay'

    elif clay >= 40 and silt >= 40:
        textural_class = 'silty clay'

    elif clay >= 40 and sand <= 45 and silt < 40:
        textural_class = 'clay'

    else:
        textural_class = 'na'

    return textural_class

vector_func = np.vectorize(TextureClass)
textures = vector_func(SandArray, ClayArray, SiltArray)
rweber
  • 132
  • 7