{"id":2531,"date":"2026-02-02T10:03:21","date_gmt":"2026-02-02T10:03:21","guid":{"rendered":"https:\/\/demo.materiamedica.net\/demo6\/?p=2531"},"modified":"2026-02-02T10:03:21","modified_gmt":"2026-02-02T10:03:21","slug":"chapter-1-ufunc-intro","status":"publish","type":"post","link":"https:\/\/demo.materiamedica.net\/demo6\/chapter-1-ufunc-intro\/","title":{"rendered":"Chapter 1: ufunc Intro"},"content":{"rendered":"<h3 dir=\"auto\">1. What does \u201cufunc\u201d actually mean?<\/h3>\n<p dir=\"auto\"><strong>ufunc = universal function<\/strong><\/p>\n<p dir=\"auto\">A ufunc is a NumPy function that:<\/p>\n<ul dir=\"auto\">\n<li>works <strong>element-by-element<\/strong> on entire arrays (or scalars)<\/li>\n<li>is <strong>extremely fast<\/strong> (written in compiled C \/ Fortran code)<\/li>\n<li>automatically handles <strong>broadcasting<\/strong> (different shapes get matched smartly)<\/li>\n<li>supports many <strong>extra features<\/strong> like where, out, dtype control, etc.<\/li>\n<li>almost always returns a <strong>new array<\/strong> (very rarely modifies in place)<\/li>\n<\/ul>\n<p dir=\"auto\"><strong>The most important sentence you should remember forever<\/strong>:<\/p>\n<blockquote dir=\"auto\">\n<p dir=\"auto\">In NumPy, almost every mathematical, logical or comparison operation you want to apply to an array is actually a ufunc.<\/p>\n<\/blockquote>\n<p dir=\"auto\">That\u2019s why when you write a + b, np.sin(x), a &gt; 5 or np.sqrt(arr) \u2014 you are using ufuncs.<\/p>\n<h3 dir=\"auto\">2. Why do ufuncs feel like magic? (comparison with pure Python)<\/h3>\n<p dir=\"auto\"><strong>Pure Python way<\/strong> (slow, verbose, not vectorized)<\/p>\n<div dir=\"auto\">\n<div data-testid=\"code-block\">\n<div>\n<div>Python<\/div>\n<div>\n<pre tabindex=\"0\"><code># List of temperatures in Celsius\r\ntemps_c = [23.4, 25.1, 19.8, 28.7, 22.0, 30.5]\r\n\r\n# Convert to Fahrenheit (classic slow way)\r\ntemps_f = []\r\nfor t in temps_c:\r\n    f = t * 1.8 + 32\r\n    temps_f.append(round(f, 1))\r\n\r\nprint(temps_f)<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<p dir=\"auto\"><strong>NumPy ufunc way<\/strong> (fast, clean, beautiful)<\/p>\n<div dir=\"auto\">\n<div data-testid=\"code-block\">\n<div>\n<div>Python<\/div>\n<div>\n<pre tabindex=\"0\"><code>temps_c = np.array([23.4, 25.1, 19.8, 28.7, 22.0, 30.5])\r\n\r\ntemps_f = temps_c * 1.8 + 32\r\nprint(np.round(temps_f, 1))\r\n# [74.1 77.2 67.6 83.7 71.6 86.9]<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<p dir=\"auto\"><strong>Same result \u2014 completely different speed &amp; style.<\/strong><\/p>\n<h3 dir=\"auto\">3. The four big categories of ufuncs you will use every day<\/h3>\n<h4 dir=\"auto\">Category 1 \u2013 Arithmetic ufuncs<\/h4>\n<p dir=\"auto\">These are the ones you use most often:<\/p>\n<div dir=\"auto\">\n<div data-testid=\"code-block\">\n<div>\n<div>Python<\/div>\n<div>\n<pre tabindex=\"0\"><code>a = np.arange(1, 11)\r\nb = np.arange(10, 0, -1)\r\n\r\nprint(\"a =\", a)\r\nprint(\"b =\", b)\r\n\r\nprint(\"a + b   =\", a + b)\r\nprint(\"a - b   =\", a - b)\r\nprint(\"a * b   =\", a * b)\r\nprint(\"a \/ b   =\", a \/ b.round(2))     # floating point\r\nprint(\"a \/\/ b  =\", a \/\/ b)             # integer division\r\nprint(\"a ** 2  =\", a ** 2)\r\nprint(\"a % 3   =\", a % 3)\r\nprint(\"a + 100 =\", a + 100)            # broadcasting scalar<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<h4 dir=\"auto\">Category 2 \u2013 Mathematical &amp; transcendental ufuncs<\/h4>\n<div dir=\"auto\">\n<div data-testid=\"code-block\">\n<div>\n<div>Python<\/div>\n<div>\n<pre tabindex=\"0\"><code>x = np.linspace(0, 2*np.pi, 13)\r\n\r\nprint(\"x (radians) =\", x.round(3))\r\n\r\nprint(\"sin(x) =\", np.sin(x).round(3))\r\nprint(\"cos(x) =\", np.cos(x).round(3))\r\nprint(\"tan(x) =\", np.tan(x).round(3))\r\nprint(\"exp(x) =\", np.exp(x).round(2))\r\nprint(\"log(x+1) =\", np.log1p(x).round(3))   # log(1+x) \u2014 safer for small x\r\nprint(\"sqrt(x) =\", np.sqrt(x).round(3))\r\nprint(\"abs(x-3) =\", np.abs(x-3).round(3))<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<h4 dir=\"auto\">Category 3 \u2013 Rounding &amp; truncation ufuncs<\/h4>\n<div dir=\"auto\">\n<div data-testid=\"code-block\">\n<div>\n<div>Python<\/div>\n<div>\n<pre tabindex=\"0\"><code>values = np.array([-2.7, -1.4, -0.8, 0.3, 1.6, 2.9, 3.2])\r\n\r\nprint(\"floor  :\", np.floor(values))\r\nprint(\"ceil   :\", np.ceil(values))\r\nprint(\"trunc  :\", np.trunc(values))\r\nprint(\"round  :\", np.round(values, decimals=0))\r\nprint(\"rint   :\", np.rint(values))     # round to nearest even<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<h4 dir=\"auto\">Category 4 \u2013 Comparison &amp; logical ufuncs<\/h4>\n<p dir=\"auto\">They return <strong>boolean arrays<\/strong> \u2014 extremely useful for masking\/filtering.<\/p>\n<div dir=\"auto\">\n<div data-testid=\"code-block\">\n<div>\n<div>Python<\/div>\n<div>\n<pre tabindex=\"0\"><code>a = np.array([3, 8, 1, 9, 4, 6, 2, 7, 5, 0])\r\n\r\nprint(\"a &gt; 5     :\", a &gt; 5)\r\nprint(\"a == 4    :\", a == 4)\r\nprint(\"a &gt;= 3    :\", a &gt;= 3)\r\nprint(\"(a % 2) == 0 :\", (a % 2) == 0)   # even numbers<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<h3 dir=\"auto\">4. Broadcasting \u2013 the feature that makes ufuncs feel magical<\/h3>\n<p dir=\"auto\">ufuncs automatically stretch smaller arrays to match the shape of larger ones.<\/p>\n<div dir=\"auto\">\n<div data-testid=\"code-block\">\n<div>\n<div>Python<\/div>\n<div>\n<pre tabindex=\"0\"><code>a = np.arange(12).reshape(3, 4)\r\nprint(\"a =\\n\", a)\r\n\r\n# Scalar broadcasts to whole array\r\nprint(\"\\na + 100:\\n\", a + 100)\r\n\r\n# 1D row vector broadcasts across rows\r\nrow_vec = np.array([100, 200, 300, 400])\r\nprint(\"\\na + row_vec:\\n\", a + row_vec)\r\n\r\n# Column vector (needs shape (3,1) or use [:, np.newaxis])\r\ncol_vec = np.array([10, 20, 30])[:, np.newaxis]\r\nprint(\"\\na + col_vec:\\n\", a + col_vec)<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<p dir=\"auto\"><strong>Golden rule about broadcasting<\/strong>:<\/p>\n<blockquote dir=\"auto\">\n<p dir=\"auto\">NumPy compares shapes from right to left. Dimensions are compatible if they are equal <strong>or<\/strong> one of them is 1 (it gets stretched).<\/p>\n<\/blockquote>\n<h3 dir=\"auto\">5. Very common realistic patterns you will write again and again<\/h3>\n<p dir=\"auto\"><strong>Pattern 1 \u2013 Normalize \/ standardize features<\/strong><\/p>\n<div dir=\"auto\">\n<div data-testid=\"code-block\">\n<div>\n<div>Python<\/div>\n<div>\n<pre tabindex=\"0\"><code>X = np.random.randn(10000, 20)\r\n\r\nX_norm = (X - X.mean(axis=0)) \/ X.std(axis=0)<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<p dir=\"auto\"><strong>Pattern 2 \u2013 Clip values to valid range<\/strong><\/p>\n<div dir=\"auto\">\n<div data-testid=\"code-block\">\n<div>\n<div>Python<\/div>\n<div>\n<pre tabindex=\"0\"><code>pixels = np.random.randint(-80, 350, size=(200, 300))\r\n\r\nvalid_pixels = np.clip(pixels, 0, 255)<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<p dir=\"auto\"><strong>Pattern 3 \u2013 Element-wise conditional replacement<\/strong><\/p>\n<div dir=\"auto\">\n<div data-testid=\"code-block\">\n<div>\n<div>Python<\/div>\n<div>\n<pre tabindex=\"0\"><code>values = np.random.randn(5000)\r\n\r\n# Replace negatives with 0\r\nvalues[values &lt; 0] = 0\r\n\r\n# Or using np.where (also a ufunc!)\r\ncleaned = np.where(values &lt; 0, 0, values)<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<p dir=\"auto\"><strong>Pattern 4 \u2013 Vectorized math on images<\/strong><\/p>\n<div dir=\"auto\">\n<div data-testid=\"code-block\">\n<div>\n<div>Python<\/div>\n<div>\n<pre tabindex=\"0\"><code>img = np.random.randint(0, 256, (300, 400, 3), dtype=np.uint8)\r\n\r\n# Increase contrast\r\ncontrasted = np.clip(1.3 * img - 40, 0, 255).astype(np.uint8)\r\n\r\n# Simple grayscale\r\ngray = np.mean(img, axis=2).astype(np.uint8)<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<p dir=\"auto\"><strong>Pattern 5 \u2013 Find elements that satisfy conditions<\/strong><\/p>\n<div dir=\"auto\">\n<div data-testid=\"code-block\">\n<div>\n<div>Python<\/div>\n<div>\n<pre tabindex=\"0\"><code>scores = np.random.randint(40, 101, 300)\r\n\r\npassed = scores &gt;= 60\r\nprint(\"Pass rate:\", passed.mean().round(3))\r\n\r\n# Get actual passing scores\r\npassing_scores = scores[passed]<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<h3 dir=\"auto\">Summary \u2013 ufunc Quick Reference (keep this handy)<\/h3>\n<div>\n<div dir=\"auto\">\n<table dir=\"auto\">\n<thead>\n<tr>\n<th data-col-size=\"sm\">Category<\/th>\n<th data-col-size=\"lg\">Typical ufuncs you will type often<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td data-col-size=\"sm\">Arithmetic<\/td>\n<td data-col-size=\"lg\">+, -, *, \/, \/\/, %, **, np.add, np.subtract\u2026<\/td>\n<\/tr>\n<tr>\n<td data-col-size=\"sm\">Trigonometric<\/td>\n<td data-col-size=\"lg\">np.sin, np.cos, np.tan, np.arcsin, np.arccos\u2026<\/td>\n<\/tr>\n<tr>\n<td data-col-size=\"sm\">Exponential &amp; log<\/td>\n<td data-col-size=\"lg\">np.exp, np.log, np.log10, np.log1p, np.expm1<\/td>\n<\/tr>\n<tr>\n<td data-col-size=\"sm\">Rounding<\/td>\n<td data-col-size=\"lg\">np.floor, np.ceil, np.round, np.trunc, np.rint<\/td>\n<\/tr>\n<tr>\n<td data-col-size=\"sm\">Comparison \/ logical<\/td>\n<td data-col-size=\"lg\">&gt;, &gt;=, &lt;, &lt;=, ==, !=, np.logical_and, np.logical_or<\/td>\n<\/tr>\n<tr>\n<td data-col-size=\"sm\">Maximum \/ minimum<\/td>\n<td data-col-size=\"lg\">np.maximum, np.minimum, np.fmax, np.fmin<\/td>\n<\/tr>\n<tr>\n<td data-col-size=\"sm\">Absolute \/ sign<\/td>\n<td data-col-size=\"lg\">np.abs, np.sign, np.negative<\/td>\n<\/tr>\n<tr>\n<td data-col-size=\"sm\">Specialized<\/td>\n<td data-col-size=\"lg\">np.sqrt, np.square, np.cbrt, np.reciprocal, np.hypot<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div><\/div>\n<\/div>\n<\/div>\n<h3 dir=\"auto\">Final teacher advice (very important)<\/h3>\n<p dir=\"auto\"><strong>Golden rule #1<\/strong> If you are writing a Python for loop to apply the same math operation to every element of a NumPy array \u2192 <strong>you are almost certainly doing it wrong<\/strong>.<\/p>\n<p dir=\"auto\"><strong>Golden rule #2<\/strong> When you think \u201cI need to do [some math] to every element\u201d, try to write it directly with ufuncs + broadcasting first.<\/p>\n<p dir=\"auto\"><strong>Golden rule #3<\/strong> When in doubt \u2192 look at the NumPy cheat sheet or type np.&lt;tab&gt; in Jupyter \u2014 you will almost always find a ufunc that does exactly what you want.<\/p>\n<p dir=\"auto\">Would you like to go deeper into any of these topics?<\/p>\n<ul dir=\"auto\">\n<li>Advanced broadcasting examples with 3D\/4D arrays<\/li>\n<li>ufuncs with out= parameter (in-place operations)<\/li>\n<li>Creating your own ufunc (np.frompyfunc, np.vectorize)<\/li>\n<li>Common performance traps when using ufuncs<\/li>\n<li>Realistic mini-project: vectorized image processing \/ data cleaning<\/li>\n<\/ul>\n<p dir=\"auto\">Just tell me what feels most useful for you right now! \ud83d\ude0a<\/p>\n","protected":false},"excerpt":{"rendered":"<p>1. What does \u201cufunc\u201d actually mean? ufunc = universal function A ufunc is a NumPy function that: works element-by-element on entire arrays (or scalars) is extremely fast (written in compiled C \/ Fortran code)&#46;&#46;&#46;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[75],"tags":[],"class_list":["post-2531","post","type-post","status-publish","format-standard","hentry","category-numpy"],"_links":{"self":[{"href":"https:\/\/demo.materiamedica.net\/demo6\/wp-json\/wp\/v2\/posts\/2531","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/demo.materiamedica.net\/demo6\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/demo.materiamedica.net\/demo6\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/demo.materiamedica.net\/demo6\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/demo.materiamedica.net\/demo6\/wp-json\/wp\/v2\/comments?post=2531"}],"version-history":[{"count":1,"href":"https:\/\/demo.materiamedica.net\/demo6\/wp-json\/wp\/v2\/posts\/2531\/revisions"}],"predecessor-version":[{"id":2532,"href":"https:\/\/demo.materiamedica.net\/demo6\/wp-json\/wp\/v2\/posts\/2531\/revisions\/2532"}],"wp:attachment":[{"href":"https:\/\/demo.materiamedica.net\/demo6\/wp-json\/wp\/v2\/media?parent=2531"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/demo.materiamedica.net\/demo6\/wp-json\/wp\/v2\/categories?post=2531"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/demo.materiamedica.net\/demo6\/wp-json\/wp\/v2\/tags?post=2531"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}