Chapter 2: ufunc Create Function

1. Why would you ever need to create your own ufunc?

NumPy already has hundreds of ufuncs (sin, exp, maximum, logical_and, etc.), so why create more?

Real reasons you will actually need this:

  • You have a custom mathematical function that you want to apply element-wise to arrays
  • You want that function to be fast (vectorized), support broadcasting, and work nicely with where=, out=, etc.
  • You want to use it in ufunc-style expressions like a + myfunc(b) * c
  • You’re writing reusable numerical code (library, module, package) and want it to feel “native NumPy”

2. The four main ways to create element-wise functions (from worst to best)

Method Speed Broadcasting ufunc features (out, where, reduce…) When to use
Python for-loop very slow no no never (for learning only)
np.vectorize slow yes partial quick prototypes, non-numeric functions
np.frompyfunc medium yes full ufunc behavior when you really need ufunc methods
Write in C / Cython / Numba very fast yes full ufunc behavior performance-critical code

Today we’ll focus on the two practical NumPy-native ways:

  • np.vectorize (easiest, but slowest)
  • np.frompyfunc (true ufunc behavior)

3. Method 1 – np.vectorize (the beginner-friendly way)

vectorize takes a normal Python function and turns it into something that looks like it works element-wise.

Python

Important things you should notice

Python

But… it is still slow

Python

When to use vectorize

  • Quick prototypes
  • Functions that cannot be easily written with NumPy operations
  • Non-numeric functions (strings, objects, conditionals that are hard to vectorize)
  • You don’t care about maximum speed

4. Method 2 – np.frompyfunc (true ufunc behavior)

frompyfunc creates a real ufunc — it supports reduce, accumulate, outer, at, reduceat, where, out, etc.

Python

ufunc features you now get for free

Python

5. Realistic examples – when you actually create custom ufuncs

Example 1 – Huber loss (smooth L1 loss)

Python

Example 2 – Swish activation (modern deep learning)

Python

6. Summary – Which method should you choose?

Situation Recommended tool
Quick test / prototype / non-numeric func np.vectorize
You want real ufunc features (reduce, accumulate, outer…) np.frompyfunc
You need maximum performance Numba @vectorize, Cython, or hand-written C extension
You can write it with existing NumPy ops just write it normally — no need for custom ufunc

Final teacher advice (very important)

Golden rule #1 Try very hard to avoid creating a custom ufunc if you can express the operation using existing ufuncs + broadcasting + boolean indexing + np.where.

Golden rule #2 If you do need a custom function → start with np.vectorize for quick testing, then switch to np.frompyfunc if you need real ufunc behavior.

Golden rule #3 If speed matters a lot → learn Numba @vectorize or @guvectorize — it is usually much faster than frompyfunc.

Would you like to continue with any of these topics?

  • Writing ufuncs with Numba (much faster than frompyfunc)
  • Using frompyfunc with multiple inputs / outputs
  • Creating ufuncs that work with dtype=object (strings, dates…)
  • Realistic mini-project: custom activation functions + compare speed
  • Common performance traps when using custom ufuncs

Just tell me what you want to focus on next! 😊

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *