<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged datascience</title>
<link>https://coderlegion.com/tag/datascience</link>
<description>Powered by Question2Answer</description>
<item>
<title>Advanced SQL Analytics for Data Analysis.</title>
<link>https://coderlegion.com/31618/advanced-sql-analytics-for-data-analysis</link>
<description>&lt;h2 id=&quot;advanced-sql-analytics-for-running-a-hospital-database-&quot;&gt;Advanced SQL analytics for running a hospital database.&lt;/h2&gt;
&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Structured Query Language is essential and common in most industry sectors. In healthcare reporting, SQL plays a critical role in analytics. PostgreSQL lets data scientists take those transaction records and pull out operational insights, so you can make decisions. In this guide we go through the main SQL techniques, using a hospital database schema; patients , doctors, departments , wards, diagnoses, procedures, and visits. &lt;/p&gt;
&lt;p&gt;First, before anything else we need to create our database schema. Connect to PostgerSQL DBMS and run a new sql script to start.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a schema &lt;code&gt;hospital&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;create schema hospital;&lt;/code&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check to which schema you are connected.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;show search_path;&lt;/code&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If connected to a different schema, set connection to your &lt;code&gt;hospital&lt;/code&gt; schema.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;set search_path to hospital;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now you are set to begin creating your tables. Run the following codes to create the different tables and insert data into the newly created tables:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Doctors Table&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;--create doctors table

create table hospital.doctors (
doctor_id int primary key,
doctor_name varchar(50) not null,
doctor_gender varchar(20),
doctor_specialty varchar(20),
doctor_experience_years int
);

select * from hospital.doctors;

-- insert data into doctors

insert into hospital.doctors (doctor_id, doctor_name, doctor_gender, doctor_specialty, doctor_experience_years)
values 
(101, 'DR.Brian Mwangi', 'Male', 'Cardiologist', 12),
(102, 'Dr. Sarah Wanjiku', 'Female', 'General Physician', 9), 
(103, 'Dr. Peter Otieno', 'Male', 'Orthopedic Surgeon', 15),
(104, 'Dr. Faith Chebet', 'Female', 'Pediatrician', 8);

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Departments Table&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- create departments table

create table hospital.department (
department_id int primary key,
department_name varchar (50) not null
);


select * from hospital.department;

-- insert data into departments

insert into hospital.department (department_id, department_name)
values 
(10, 'Cardiology'),
(20, 'General Medicine'),
(30, 'Orthopedics'),
(40, 'Pediatrics');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Departments Table&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- create diagnosis table

create table hospital.diagnosis (
diagnosis_id int primary key,
diagnosis_name varchar(50),
diagnosis_category varchar(50)
);

select * from hospital.diagnosis;

--insert data into diagnosis table

insert into hospital.diagnosis (diagnosis_id, diagnosis_name, diagnosis_category)
values 
(501,'Hypertension', 'Chronic Disease'),
(502, 'Malaria', 'Infectious Disease'),
(503, 'Fracture', 'Injury'),
(504, 'Pneumonia', 'Respiratory Disease'),
(505, 'Diabetes', 'Chronic Disease'),
(506, 'Typhoid', 'Infectious'),
(507, 'Asthma', 'Respiratory Disease'),
(508, 'Back Pian', 'Musculoskeletal'),
(509, 'Gastritis', 'Digestive Disease'),
(510, 'Heart Failure', 'Chronic Disease'),
(511, 'Flu', 'Respiratoey Disease'),
(512, 'Kidney Infection', 'Infectious Disease'),
(513, 'Anaemia', 'Blood Disorder'),
(514, 'Sprain', 'Injury'),
(515, 'Gastroenteritis', 'Digestive Disease'),
(516, 'Dislocation', 'Injury'),
(517, 'Migraine', 'Neurological Disorder'),
(518, 'Chest Pain', 'Cardiovascular Disease');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Wards Table&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- create wards table 

create table hospital.wards (
ward_id int primary key, 
ward_name varchar (50)
);

-- insert data into wards
insert into hospital.wards (ward_id, ward_name)
values 
(100, 'Cardiac Care Unit'),
(200, 'General Ward'),
(300, 'Surgical Ward'),
(400, 'Childrens Ward');

select * from hospital.wards;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Patients Table&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- create patients table 
create table hospital.patients (
patient_id serial primary key, 
patient_name varchar(50) not null,
patient_gender varchar(20),
patient_age int,
county varchar(30),
sub_county varchar(30)
);

--insert data into patients
insert into hospital.patients (patient_name, patient_gender, patient_age, county, sub_county)
values 
('Amina Otieno', 'Female', 34, 'Nairobi', 'Westlands'),
('John Kariuki', 'Male', 42,' Kiambu', 'Thika'),
('Grace Naliaka', 'Female', 28, 'Kakamega', 'Lurambi'),
('David Mutua', 'Male', 55, 'Machakos', 'Athi River'),
('Esther Achieng', 'Female', 63, 'Kisumu', 'Kisumu Central'),
('Mohamed Ali', 'Male', 19, 'Mombasa', 'Nyali'),
('Lucy Wambui', 'Female', 7, 'Nakuru', 'Naivasha'),
('Samuel Kiptoo', 'Male', 39, 'Uasin Gishu', 'Eldoret East'),
('Mary Wairumu', 'Female', 31, 'Murang''a', 'Kiharu'),
('Peter Ouma', 'Male', 46, 'Siaya', 'Bondo'),
('Brian Omondi', 'Male', 24, 'Kisumu', 'Kisumu East'),
('Catherine Musyoka', 'Female', 52, 'Kitui', 'Kitui Central'),
('Daniel Maina', 'Male', 16, 'Nyeri', 'Othaya'),
('Eunice Moraa', 'Female', 44, 'Kisii', 'Bonchari'),
('Francis Njuguna', 'Male', 60, 'Laikipia', 'Nyanyuki'),
('Hellen Jepchirchir', 'Female', 37, 'Bomet', 'Sotik'),
('Isaac Kamau', 'Male', 29, 'Nairobi', 'Kasarani'),
('Jane Muthoni', 'Female', 22, 'Meru', 'Imenti North'),
('Kevin Barasa', 'Male', 48, 'Bungoma', 'Webuye'),
('Lilian Adhiambo', 'Female', 26, 'Homa Bay', 'Rangwe'),
('Moses Cheruiyot', 'Male', 33, 'Kericho', 'Ainamoi'),
('Nadia Hussein', 'Female', 5, 'Garissa', 'Dadaab'),
('Patrick Muli', 'Male', 51, 'Makueni', 'Wote'),
('Ruth Nekesa', 'Female', 41, 'Trans Nzoia', 'Kitale'),
('Josephine Wekesa', 'Female', 36, 'Busia', 'Matayos');

select * from hospital.patients;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Procedures Table&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;create table hospital.procedures (
procedure_id int primary key,
procedure_name varchar (50) not null,
procedure_category varchar (20)
);

--insert data into procedures
insert into hospital.procedures (procedure_id, procedure_name, procedure_category)
values
(701, 'Blood Pressure Monitoring', 'Diagnostic'),
(702, 'Malaria Test', 'Laboratory'),
(703, 'X-ray', 'Imaging'),
(704, 'Chest X-ray', 'Imaging'),
(705, 'Blood Sugar Test', 'Laboratory'),
(706, 'Full Blood Count', 'Laboratory'),
(707, 'Nebulization', 'Treatment'),
(708, 'Physiotherapy', 'Treatment'),
(709, 'Endoscopy', 'Diagnostic'),
(710, 'ECG', 'Diagnostic'),
(711, 'Consultation', 'Consultation'),
(712, 'Joint Support Bandage', 'Treatment'),
(713, 'IV Fluids', 'Treatment'),
(714, 'Closed Reduction', 'Treatment'),
(715, 'Pain Management', 'Treatment');


select * from hospital.procedures;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Visits Table&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- create visits table (Fact Table)

create table hospital.visits (
visit_id int primary key,
visit_date date not null,
patient_id int not null references hospital.patients(patient_id),
doctor_id int not null references hospital.doctors(doctor_id),
department_id int not null references hospital.department(department_id),
ward_id int references hospital.wards(ward_id),
diagnosis_id int references hospital.diagnosis(diagnosis_id),
procedure_id int references hospital.procedures(procedure_id),
admission_type varchar(20) not null
    check (admission_type in ('Outpatient', 'Inpatient', 'Emergency')),
length_of_stay_days int default 0
    check (length_of_stay_days &amp;gt;= 0),
triage_level varchar(10)
    check (triage_level in ('Red', 'Orange', 'Yellow', 'Green')),
insurance_provider varchar(50),
total_bill_kes numeric(10,2) not null
    check (total_bill_kes &amp;gt;= 0),
payment_method varchar(20),
follow_up_required varchar(3)
    check (follow_up_required in ('Yes', 'No')),
patient_satisfaction_score numeric(3,1)
    check (patient_satisfaction_score between 0 and 5)
);

--insert data into visits table
insert into hospital.visits (visit_id, visit_date, patient_id, doctor_id, department_id, ward_id, diagnosis_id,
procedure_id, admission_type, length_of_stay_days, triage_level, insurance_provider, total_bill_kes, payment_method,
follow_up_required, patient_satisfaction_score)
values
(1001, '2026-06-01', 1, 101, 10, 100, 501, 701, 'Outpatient', 0, 'Green', 'NHIF', 3500, 'Cash', 'Yes', 4.5),
(1002, '2026-06-01', 2, 102, 20, 200, 502, 702, 'Outpatient', 0, 'Yellow', 'Britam', 2500, 'Insurance', 'No', 4.1),
(1003, '2026-06-02', 3, 103, 30, 300, 503, 703, 'Inpatient', 3, 'Red', 'AAR', 28000, 'Insurance', 'Yes', 4.3),
(1004, '2026-06-02', 4, 104, 40, 400, 504, 704, 'Inpatient', 4, 'Red', 'None', 42000, 'Cash', 'Yes', 3.9),
(1005, '2026-06-03', 5, 101, 10, 100, 505, 705, 'Outpatient', 0, 'Yellow', 'CIC', 4000, 'Mobile Money', 'Yes', 4.7),
(1006, '2026-06-03', 6, 102, 20, 200, 506, 706, 'Outpatient', 0, 'Green', 'None', 3000, 'Cash', 'No', 4.0),
(1007, '2026-06-04', 7, 104, 40, 400, 507, 707, 'Outpatient', 0, 'Yellow', 'NHIF', 5500, 'Insurance', 'Yes', 4.6),
(1008, '2026-06-04', 8, 103, 30, 300, 508, 708, 'Outpatient', 0, 'Green', 'None', 6500, 'Cash', 'Yes', 4.2),
(1009, '2026-06-05', 9, 102, 20, 200, 509, 709, 'Outpatient', 0, 'Yellow', 'Jubilee', 15000, 'Insurance', 'No', 3.8),
(1010, '2026-06-05', 10, 101, 10, 100, 510, 710, 'Inpatient', 5, 'Red', 'AAR', 76000, 'Insurance', 'Yes', 4.4),
(1011, '2026-06-06', 1, 101, 10, 100, 505, 705, 'Outpatient', 0, 'Green', 'NHIF', 4200, 'Mobile Money', 'Yes', 4.8),
(1012, '2026-06-06', 11, 102, 20, 200, 502, 702, 'Outpatient', 0, 'Green', 'None', 2300, 'Cash', 'No', 4.0),
(1013, '2026-06-07', 12, 101, 10, 100, 501, 710, 'Outpatient', 0, 'Yellow', 'Britam', 7000, 'Insurance', 'Yes', 4.5),
(1014, '2026-06-07', 13, 104, 40, 400, 511, 711, 'Outpatient', 0, 'Green', 'None', 1800, 'Cash', 'No', 4.1),
(1015, '2026-06-08', 14, 103, 30, 300, 503, 703, 'Inpatient', 2, 'Red', 'CIC', 24000, 'Mobile Money', 'Yes', 4.6),
(1016, '2026-06-08', 15, 102, 20, 200, 512, 706, 'Inpatient', 3, 'Orange', 'Jubilee', 36000, 'Insurance', 'Yes', 3.7),
(1017, '2026-06-09', 16, 101, 10, 100, 510, 710, 'Inpatient', 6, 'Red', 'AAR', 82000, 'Insurance', 'Yes', 4.2),
(1018, '2026-06-09', 17, 102, 20, 200, 509, 711, 'Outpatient', 0, 'Green', 'None', 2000, 'Cash', 'No', 4.0),
(1019, '2026-06-10', 18, 104, 40, 400, 507, 707, 'Outpatient', 0, 'Yellow', 'NHIF', 5200, 'Mobile Money', 'Yes', 4.7),
(1020, '2026-06-10', 19, 103, 30, 300, 508, 708, 'Outpatient', 0, 'Green', 'Britam', 6500, 'Insurance', 'No', 4.1),
(1021, '2026-06-11', 2, 101, 10, 100, 501, 701, 'Outpatient', 0, 'Yellow', 'Britam', 3800, 'Insurance', 'Yes', 4.3),
(1022, '2026-06-11', 20, 102, 20, 200, 513, 706, 'Outpatient', 0, 'Green', 'None', 3200, 'Mobile Money', 'No', 4.4),
(1023, '2026-06-12', 21, 103, 30, 300, 514, 712, 'Outpatient', 0, 'Yellow', 'NHIF', 7200, 'Cash', 'Yes', 4.0),
(1024, '2026-06-12', 22, 104, 40, 400, 515, 713, 'Inpatient', 2, 'Orange', 'None', 18500, 'Cash', 'Yes', 4.5),
(1025, '2026-06-13', 5, 101, 10, 100, 501, 710, 'Outpatient', 0, 'Yellow', 'CIC', 7600, 'Insurance', 'Yes', 4.6),
(1026, '2026-06-13', 23, 102, 20, 200, 506, 706, 'Inpatient', 2, 'Orange', 'Jubilee', 29000, 'Insurance', 'Yes', 3.9),
(1027, '2026-06-14', 24, 103, 30, 300, 516, 714, 'Inpatient', 1, 'Red', 'AAR', 33000, 'Insurance', 'Yes', 4.2),
(1028, '2026-06-14', 7, 104, 40, 400, 511, 711, 'Outpatient', 0, 'Green', 'NHIF', 1900, 'Mobile Money', 'No', 4.8),
(1029, '2026-06-15', 25, 102, 20, 200, 517, 715, 'Outpatient', 0, 'Yellow', 'Britam', 4800, 'Cash', 'Yes', 4.1),
(1030, '2026-06-15', 10, 101, 10, 100, 518, 710, 'Emergency', 1, 'Red', 'AAR', 22000, 'Insurance', 'Yes', 4.4);

select * from hospital.visits;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After creating our database schema, we will begin modelling the data using different advanced SQL functions.&lt;/p&gt;
&lt;h3 id=&quot;1-sql-window-functions&quot;&gt;1. SQL Window Functions&lt;/h3&gt;
&lt;p&gt;Window functions do computations on related rows in a table while maintaining the individuality of each rows.&lt;/p&gt;
&lt;h4 id=&quot;over-partition-by-order-by-and-window-frames&quot;&gt;OVER, PARTITION BY, ORDER BY, and Window Frames&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;OVER&lt;/code&gt; clause describes the rows for the window function operation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PARTITION BY&lt;/code&gt; defines different partitions for each result set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ORDER BY&lt;/code&gt; defines row order in each partition.&lt;/li&gt;
&lt;li&gt;Window frames define different limits such as&lt;code&gt;ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW&lt;/code&gt; for cumulative sums and &lt;code&gt;ROWS BETWEEN 2 PRECEDING AND CURRENT ROW&lt;/code&gt; for moving averages.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;ranking-window-functions&quot;&gt;Ranking Window Functions&lt;/h4&gt;
&lt;p&gt;Ranking functions provide numeric ordering according to specified criteria.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ROW_NUMBER&lt;/code&gt; provides a distinct numeric integer value to each row within a partition.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RANK&lt;/code&gt; provides identical rank values to ties, but skips other numbers.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DENSE_RANK&lt;/code&gt; provides identical rank values to ties, but does not skip numbers.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NTILE&lt;/code&gt; divides rows in partitions into buckets of equal size.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;How can hospital management rank inpatient visits by total bill within each admission type?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select 
    v.visit_id,
    p.patient_name,
    v.admission_type,
    v.total_bill_kes,
    row_number() over (partition by v.admission_type order by v.total_bill_kes desc) as row_num,
    rank() over (partition by v.admission_type order by v.total_bill_kes desc) as rnk,
    dense_rank() over (partition by v.admission_type order by v.total_bill_kes desc) as dense_rnk,
    ntile(2) over (partition by v.admission_type order by v.total_bill_kes desc) as bill_tier
from hospital.visits v
join hospital.patients p on v.patient_id = p.patient_id
where v.admission_type = 'Inpatient'
limit 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/m1wdvqqfnf9c1tdhy7l2.png&quot; alt=&quot;Visit Report&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ROW_NUMBER&lt;/code&gt; gives values from 1 to 5. &lt;/li&gt;
&lt;li&gt;The values obtained by &lt;code&gt;RANK&lt;/code&gt; and &lt;code&gt;DENSE_RANK&lt;/code&gt; will be same as there are no duplicate bill values. &lt;/li&gt;
&lt;li&gt;NTILE assigns the best bills to Tier 1.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;value-amp-navigation-window-function&quot;&gt;Value &amp;amp; Navigation Window Function&lt;/h4&gt;
&lt;p&gt;Navigation functions retrieve values based on relative rows.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LAG&lt;/code&gt; retrieves values from previous row.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LEAD&lt;/code&gt; retrieves values from next row.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FIRST_VALUE&lt;/code&gt; gives the first value of the expression in the frame.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LAST_VALUE&lt;/code&gt; gives the last value of the expression in the frame bounds like &lt;code&gt;ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;How can we monitor patient billing process through multiple hospital visits?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select 
    v.visit_id,
    v.patient_id,
    v.visit_date,
    v.total_bill_kes,
    lag(v.total_bill_kes, 1, 0.00) over (partition by v.patient_id order by v.visit_date) as prev_bill,
    lead(v.total_bill_kes, 1, 0.00) over (partition by v.patient_id order by v.visit_date) as next_bill,
    first_value(v.total_bill_kes) over (partition by v.patient_id order by v.visit_date) as initial_bill,
    last_value(v.total_bill_kes) over (
        partition by v.patient_id 
        order by v.visit_date 
        rows between unbounded preceding and unbounded following
    ) as final_bill
from visits v
where v.patient_id in (1, 2)
order by v.patient_id, v.visit_date;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/3iqglknz6vze0tocm3o6.png&quot; alt=&quot;Patient Billing Process Monitoring&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LAG&lt;/code&gt; retrieves previous visit invoices and &lt;code&gt;LEAD&lt;/code&gt; retrieves future visit invoices.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FIRST_VALUE&lt;/code&gt; and &lt;code&gt;LAST_VALUE&lt;/code&gt; store the first and last visit invoices respectively.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;aggregate-window-functions&quot;&gt;Aggregate Window Functions&lt;/h4&gt;
&lt;p&gt;Aggregate functions &lt;code&gt;SUM&lt;/code&gt;, &lt;code&gt;AVG&lt;/code&gt;, &lt;code&gt;COUNT&lt;/code&gt;, &lt;code&gt;MIN&lt;/code&gt;, and &lt;code&gt;MAX&lt;/code&gt; calculate moving sums and moving averages when combined with &lt;code&gt;OVER&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;In what way do we determine cumulative income, rolling average billings, categories, and billings for departments on each visit?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select 
    v.visit_id,
    v.visit_date,
    v.total_bill_kes,
    sum(v.total_bill_kes) over (order by v.visit_date, v.visit_id) as cumulative_revenue,
    avg(v.total_bill_kes) over (
        order by v.visit_date 
        rows between 2 preceding and current row
    ) as rolling_avg_bill,
    count(*) over (partition by v.admission_type) as total_admission_type_count,
    min(v.total_bill_kes) over (partition by v.admission_type) as min_admission_bill,
    max(v.total_bill_kes) over (partition by v.admission_type) as max_admission_bill
from hospital.visits v
order by v.visit_date, v.visit_id
limit 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ft1sqrdykuhpi7j0ehnv.png&quot; alt=&quot;Aggregate Window Functions&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SUM&lt;/code&gt; forms a running total. &lt;/li&gt;
&lt;li&gt;&lt;code&gt;AVG&lt;/code&gt; calculates a 3 row moving average. &lt;/li&gt;
&lt;li&gt;&lt;code&gt;COUNT&lt;/code&gt;, &lt;code&gt;MIN&lt;/code&gt;, and &lt;code&gt;MAX&lt;/code&gt; present group statistics without collapsing rows.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-sql-joins&quot;&gt;2. SQL Joins&lt;/h3&gt;
&lt;p&gt;Joins combine columns from multiple tables using related key columns.&lt;/p&gt;
&lt;h4 id=&quot;inner-join&quot;&gt;INNER JOIN&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;INNER JOIN&lt;/code&gt; returns rows with matching key values in both tables.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;How do we generate a visit report combining patient names, attending doctors, and clinical departments?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select 
    v.visit_id,
    p.patient_name,
    d.doctor_name,
    dept.department_name,
    v.total_bill_kes
from hospital.visits v
inner join hospital.patients p on v.patient_id = p.patient_id
inner join hospital.doctors d on v.doctor_id = d.doctor_id
inner join hospital.department dept on v.department_id = dept.department_id
limit 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/cpriilp4a3qc6w9wh6v5.png&quot; alt=&quot;Inner Join&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;INNER JOIN&lt;/code&gt; Matches keys across visits, patients, doctors, and department tables.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;left-join&quot;&gt;LEFT JOIN&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;LEFT JOIN&lt;/code&gt; produces all the rows from the left table and the matched rows from the right table. Fields in the right table without a match produce &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;How can we study the number of visits and expenses of patients without losing those that have no visits at all?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select 
    p.patient_id,
    p.patient_name,
    count(v.visit_id) as total_visits,
    coalesce(sum(v.total_bill_kes), 0.00) as total_spent_kes
from hospital.patients p
left join hospital.visits v on p.patient_id = v.patient_id
group by p.patient_id, p.patient_name
order by total_visits desc, p.patient_id
limit 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/8osejvnidi5913phipk8.png&quot; alt=&quot;Left Join&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All patient records are retained using &lt;code&gt;LEFT JOIN&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Patients having no visits will have &lt;code&gt;COUNT&lt;/code&gt; as 0 while &lt;code&gt;COALESCE&lt;/code&gt; will convert &lt;code&gt;NULL&lt;/code&gt; sums to &lt;code&gt;0.00.&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;right-join&quot;&gt;RIGHT JOIN&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;RIGHT JOIN&lt;/code&gt; retrieves all records from right table and matching records from left table.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;How can hospital administration ensure that all physical wards are reflected in admission records?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select 
    w.ward_id,
    w.ward_name,
    v.visit_id,
    v.total_bill_kes
from hospital.visits v
right join hospital.wards w on v.ward_id = w.ward_id
where v.visit_id is null or v.visit_id &amp;lt;= 1003;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/trk5b98a06x3pfebvxub.png&quot; alt=&quot;Right Join&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All wards are kept in &lt;code&gt;RIGHT JOIN&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;The Childrens Ward will be with &lt;code&gt;NULL&lt;/code&gt; visits if no match occurs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;full-outer-join&quot;&gt;FULL OUTER JOIN&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;FULL OUTER JOIN&lt;/code&gt; will display all records if there is a match in either left or right record.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;How do we perform auditing of doctor and department records for unassigned doctors or unmapped departments?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select 
    d.doctor_name,
    dept.department_name
from hospital.doctors d
full outer join hospital.department dept on d.doctor_id = dept.department_id;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/lxx54jj6rrte3qgctuzv.png&quot; alt=&quot;Full Outer Join&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Null values will be returned in the rows where there is no relationship between doctor and department IDs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;cross-join&quot;&gt;CROSS JOIN&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;CROSS JOIN&lt;/code&gt; results in a Cartesian product where each row in the first table is paired up with each row of the second table.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;How can hospital managers get a complete list of all pairings of physicians with hospital wards for coverage planing?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select 
    d.doctor_name,
    w.ward_name
from hospital.doctors d
cross join hospital.wards w
order by d.doctor_name, w.ward_name
limit 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/1wr0ga53xrcehptw5ysf.png&quot; alt=&quot;Cross Join&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Joining 4 doctors with 4 wards results in 16 combination rows.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;self-join&quot;&gt;SELF JOIN&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;SELF JOIN&lt;/code&gt; involves joining a table to itself for comparing rows in the same table.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;In what way can readmission of patients be identified by matching their first visit to their next visit?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select 
    v1.patient_id,
    v1.visit_id as original_visit,
    v1.visit_date as original_date,
    v2.visit_id as return_visit,
    v2.visit_date as return_date
from hospital.visits v1
inner join hospital.visits v2 on v1.patient_id = v2.patient_id and v1.visit_id &amp;lt; v2.visit_id
order by v1.patient_id
limit 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/6gu6l8j459l4c0l89mg4.png&quot; alt=&quot;Self Join&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linking visits on patient ID where &lt;code&gt;v1.visit_id&lt;/code&gt; is less than &lt;code&gt;v2.visit_id&lt;/code&gt; matches distinct visits per patient.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;join-summary&quot;&gt;Join Summary&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;INNER JOIN&lt;/code&gt; matches rows in both tables.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LEFT JOIN&lt;/code&gt; keeps all left table rows.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RIGHT JOIN&lt;/code&gt; keeps all right table rows.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FULL OUTER JOIN&lt;/code&gt; keeps all rows from both tables.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CROSS JOIN&lt;/code&gt; produces Cartesian pairs.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SELF JOIN&lt;/code&gt; compares rows within one table.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-common-table-expressions-ctes-&quot;&gt;3. Common Table Expressions (CTEs)&lt;/h3&gt;
&lt;p&gt;A CTE is a named temporary table created within a &lt;code&gt;WITH&lt;/code&gt; clause.&lt;/p&gt;
&lt;h4 id=&quot;simple-ctes&quot;&gt;Simple CTEs&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;What approach should be used to identify costly inpatient treatments and connect them with patients and their physicians?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;with high_cost_visits as (
    select 
        visit_id,
        patient_id,
        doctor_id,
        total_bill_kes,
        admission_type
    from hospital.visits
    where total_bill_kes &amp;gt;= 35000.00
)
select 
    h.visit_id,
    p.patient_name,
    d.doctor_name,
    h.total_bill_kes
from high_cost_visits h
join hospital.patients p on h.patient_id = p.patient_id
join hospital.doctors d on h.doctor_id = d.doctor_id;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/lx3qdtqcxagxsp5vwmxv.png&quot; alt=&quot;Simple CTE&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CTE will filter out high cost visits first to keep the subsequent join clean.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;multiple-chained-ctes&quot;&gt;Multiple Chained CTEs&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;How do we find out department revenues, overall average, and top performing departments?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;with dept_revenue as (
    select 
        department_id,
        sum(total_bill_kes) as total_revenue,
        count(visit_id) as total_visits
    from hospital.visits
    group by department_id
),
dept_benchmark as (
    select 
        avg(total_revenue) as benchmark_avg_revenue
    from dept_revenue
)
select 
    d.department_name,
    dr.total_revenue,
    dr.total_visits,
    round(db.benchmark_avg_revenue, 2) as benchmark_avg
from dept_revenue dr
join hospital.department d on dr.department_id = d.department_id
cross join dept_benchmark db
where dr.total_revenue &amp;gt; db.benchmark_avg_revenue;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/449qhx3hzealxl7t6ncw.png&quot; alt=&quot;Multiple CTEs&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dept_revenue&lt;/code&gt; calculates the total revenue for each &lt;code&gt;department&lt;/code&gt;, &lt;code&gt;dept_benchmark&lt;/code&gt; calculates the average and the last &lt;code&gt;SELECT&lt;/code&gt; statement filters departments whose revenues are greater than the average.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;recursive-ctes&quot;&gt;Recursive CTEs&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Recursive CTEs&lt;/code&gt; create a sequence by means of an &lt;code&gt;anchor query&lt;/code&gt; and &lt;code&gt;recursive query&lt;/code&gt; linked by &lt;code&gt;UNION ALL&lt;/code&gt; operator.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;How can the hospital management create a biweekly follow up schedule?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;with recursive follow_up_schedule as (
    select 
        1 as cycle_number,
        cast('2026/07/01' as date) as scheduled_date
    union all
    select 
        cycle_number + 1,
        cast(scheduled_date + interval '14 days' as date)
    from follow_up_schedule
    where cycle_number &amp;lt; 4
)
select * from follow_up_schedule;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/w9avtbp5ixud7pfq9ysy.png&quot; alt=&quot;Recursive CTE&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cycle number 1 starts from &lt;code&gt;July 1 2026&lt;/code&gt; as the &lt;code&gt;anchor date&lt;/code&gt;, and the recursive query keeps adding 14 days till cycle number is 4.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;ctes-vs-subqueries&quot;&gt;CTEs vs. Subqueries&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;CTEs&lt;/code&gt; provide well-defined top-level modules, which can be used again and again, while subqueries within the WHERE or FROM clause tend to complicate code and cannot be executed recursively.&lt;/p&gt;
&lt;h3 id=&quot;4-sql-subqueries&quot;&gt;4. SQL Subqueries&lt;/h3&gt;
&lt;p&gt;A subquery is a query nested inside another SQL query.&lt;/p&gt;
&lt;h4 id=&quot;subquery-types&quot;&gt;Subquery Types&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Scalar&lt;/code&gt; Subquery fetches one row and one column.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Non-correlated&lt;/code&gt;Subquery executes only once and independently.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Correlated&lt;/code&gt; Subquery uses columns of outer query and executes for every row of outer query.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Nested&lt;/code&gt; Subquery uses subqueries in subqueries.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;subqueries-in-select-clauses&quot;&gt;Subqueries in SELECT Clauses&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;How can individual patient bills be shown along with the total hospital average bill?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select 
    visit_id,
    total_bill_kes,
    (select round(avg(total_bill_kes), 2) from hospital.visits) as overall_avg_bill
from hospital.visits
where visit_id between 1001 and 1005;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/gx3q0v7r1kr5sjyfeasm.png&quot; alt=&quot;Select Subqueries&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scalar subquery evaluates to average &lt;code&gt;16266.67&lt;/code&gt; for all rows.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;using-subqueries-in-from-clauses-derived-table-&quot;&gt;Using Subqueries in FROM Clauses (Derived Table)&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;How do we query the pre-aggregated metrics of patients using an inline derived table?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select 
    patient_summary.patient_id,
    patient_summary.visit_count,
    patient_summary.total_spent
from (
    select 
        patient_id,
        count(visit_id) as visit_count,
        sum(total_bill_kes) as total_spent
    from hospital.visits
    group by patient_id
) as patient_summary
where patient_summary.visit_count &amp;gt; 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/gpg7zu85w5vr1pebl4ap.png&quot; alt=&quot;From Subquery&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;FROM&lt;/code&gt; subquery groups the number of visits per patient into table alias patient_summary, subject to WHERE.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;subqueries-in-where-clause&quot;&gt;Subqueries in WHERE Clause&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;What is the correlated subquery to find visits where the total bill is greater than the average bill of that particular department?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select 
    v1.visit_id,
    v1.patient_id,
    v1.department_id,
    v1.total_bill_kes
from hospital.visits v1
where v1.total_bill_kes &amp;gt; (
    select avg(v2.total_bill_kes)
    from hospital.visits v2
    where v2.department_id = v1.department_id
)
limit 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/08o8bu15t93paib73o1b.png&quot; alt=&quot;Where Subqueries&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The inner query calculates the average billing amount for each department based on outer query department IDs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;Which physicians have admitted any patients as inpatients?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select 
    d.doctor_id,
    d.doctor_name,
    d.doctor_specialty
from hospital.doctors d
where exists (
    select 1 
    from hospital.visits v 
    where v.doctor_id = d.doctor_id 
      and v.admission_type = 'inpatient'
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/x34b2dlyj5gbu387srum.png&quot; alt=&quot;Exists&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;EXISTS&lt;/code&gt; returns true when there is a row for a visit that matches.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Question:&lt;/em&gt; &lt;em&gt;How can we find visits where the billing value is greater than all the outpatient visit bills?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;select 
    visit_id,
    patient_id,
    total_bill_kes,
    admission_type
from hospital.visits
where total_bill_kes &amp;gt; all (
    select total_bill_kes
    from hospital.visits
    where admission_type = 'outpatient'
)
limit 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Expected Results&lt;/em&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/vdfcjvi0dr5ifpb94esd.png&quot; alt=&quot;where&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explanation&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The internal query locates outpatient charges up to &lt;code&gt;15000.00&lt;/code&gt;. ALL filters visits more than &lt;code&gt;15000.00&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Understanding the concepts of window functions, joins, common table expressions, and subqueries helps in creating strong PostgreSQL queries that enable healthcare workers to review their operations and enhance financial efficiency. Such information enables healthcare providers to offer quality pateint care which meets patient outcomes. &lt;/p&gt;
&lt;h3 id=&quot;github&quot;&gt;GitHub&lt;/h3&gt;
&lt;p&gt;For the completed project visit my GitHub project &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MakoriNyachaki/sql_for_data_science&quot;&gt;Hospital Database&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31618/advanced-sql-analytics-for-data-analysis</guid>
<pubDate>Sun, 18 Oct 2026 13:54:35 +0000</pubDate>
</item>
<item>
<title>A Thousand Cleared AI Devices: What the FDA's Own List Says About How AI Gets Approved</title>
<link>https://coderlegion.com/31064/a-thousand-cleared-ai-devices-what-the-fdas-own-list-says-about-how-ai-gets-approved</link>
<description>&lt;blockquote&gt;&lt;p&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/a-thousand-cleared-ai-devices/&quot;&gt;vibeagentmaking.com&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The FDA publishes a spreadsheet of every AI-enabled medical device it has ever authorized. One row per device: a name, a company, a clearance date, a review pathway, a medical specialty. As of late March 2026 it holds 1,524 rows, and it is one of the strangest artifacts in the whole AI discourse, because it is the rare place where a decade of arguments about &quot;AI regulation&quot; collapses into something you can actually count.&lt;/p&gt;
&lt;p&gt;So this piece counts. The commentary around this list makes three standard claims: the growth curve is a hockey stick, radiology dominates, and almost everything comes through a fast lane called 510(k). All three are checkable, and here is the honest headline: two of them check out cleanly, the third is understated, and the most interesting object in the whole dataset turns out not to be in the dataset at all.&lt;/p&gt;
&lt;p&gt;One bookkeeping note first, because it sets the tone for everything else. If you go looking for the total, you will find two. The FDA's own list, as published in late March 2026, carries 1,524 entries. An independent tracker's cumulative count through December 2025 says 1,451. The 73-entry gap is not an error, exactly. The list is a living document; the agency adds entries retroactively as well as forward, so any total is a snapshot with a date on it, and any article that tells you &quot;over a thousand AI devices&quot; without saying which snapshot is quoting a number that was already moving when they wrote it down. Both figures here come from the sources named at the bottom, with their dates. Keep that habit in mind; it is the whole method of this essay in miniature.&lt;/p&gt;
&lt;h3 id=&quot;the-three-claims-computed&quot;&gt;The three claims, computed&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The hockey stick is real.&lt;/strong&gt; In 2015 the FDA authorized 6 AI/ML devices. In 2025 it authorized 295. That is roughly a 49x increase in annual volume across a decade, and it is the least surprising fact in the file. Everybody asserts the curve; the curve computes. Fine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Radiology's share computes too, and that is the more interesting result.&lt;/strong&gt; The commonly cited figure is &quot;about three-quarters,&quot; and the cumulative numbers through 2025 say 1,104 of 1,451 devices: 76.1 percent. In 2025 alone, 255 radiology AI devices cleared via 510(k). So the folk number survives contact with the data, which is worth saying out loud in an essay premised on checking rather than repeating.&lt;/p&gt;
&lt;p&gt;But sit with the pairing of those first two facts, because together they say something neither says alone. The field grew 49x, and its composition barely moved. A decade of explosive growth in &quot;AI in medicine&quot; did not diversify medicine's AI; it deepened one specialty's lead. Growth stories usually broaden as they scale: new entrants find new niches, and the long tail thickens. This one didn't. If you build or buy in this space, the list is telling you that the regulatory, technical, and commercial machinery for getting an AI device cleared is overwhelmingly machinery for getting an &lt;em&gt;imaging&lt;/em&gt; device cleared, and every other specialty is still traveling a much thinner path.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The pathway claim is understated.&lt;/strong&gt; The standard line is that &quot;most&quot; AI devices arrive via 510(k), the premarket notification route. For machine-learning-enabled Class II devices authorized in 2024, the measured split was 94.6 percent via 510(k) and 5.4 percent via De Novo. That is not &quot;most.&quot; 510(k) is nearly the only door.&lt;/p&gt;
&lt;p&gt;Which means you cannot understand this list without understanding what that door is.&lt;/p&gt;
&lt;h3 id=&quot;cleared-by-analogy&quot;&gt;Cleared by analogy&lt;/h3&gt;
&lt;p&gt;A 510(k) clearance does not ask &quot;is this device safe and effective?&quot; in the way outsiders assume. It asks: is this device &lt;em&gt;substantially equivalent&lt;/em&gt; to a device already on the market (a &quot;predicate&quot;) in intended use and technological characteristics, without raising new questions of safety or effectiveness? Demonstrate equivalence and you are cleared. The heavyweight route that runs its own clinical evidence from scratch is the exception; for AI devices, at one door in twenty, it is nearly a rounding error.&lt;/p&gt;
&lt;p&gt;If you are a developer, you already know this structure intimately, because it is transitive trust. It is the dependency tree. Nobody audits the whole stack; you trust your package because it declares a dependency on a package you trusted last year, which was trusted because of what it depended on before that. The 510(k) system is a dependency graph of safety arguments, and a clearance is an edge in it: this device is vouched for by &lt;em&gt;that&lt;/em&gt; one.&lt;/p&gt;
&lt;p&gt;And that raises the question the essay brief called the sharpest one: what do the chains look like? An AI device cleared as equivalent to an earlier device, which was cleared as equivalent to an earlier one still. How far back do these ancestries run, and what sits at the root?&lt;/p&gt;
&lt;h3 id=&quot;the-sharpest-object-is-not-in-the-file&quot;&gt;The sharpest object is not in the file&lt;/h3&gt;
&lt;p&gt;Here is the finding about the finding: you cannot compute predicate chains from the AI list, because the list does not contain the predicate field. Clearance date, pathway, company, specialty: yes. Ancestry: no. Predicate relationships live in the individual 510(k) summaries and the separate 510(k) database. Walking a chain means joining datasets and reading documents one hop at a time, because, as a 2021 PLOS One genealogy study put it plainly, &quot;the full genealogy of ancestral relationships connecting devices cleared through the 510(k) framework is not reported (only the immediate predicates of a device are listed).&quot;&lt;/p&gt;
&lt;p&gt;The regulator's own public record shows each device's parents, never its lineage. The genealogy is an inference, always and for everyone, including the agency's own readers. In dependency terms: every package declares its direct imports, and there is no lockfile. If you want the transitive closure, you crawl.&lt;/p&gt;
&lt;p&gt;People have crawled. This is not a new idea, and pretending otherwise would be theft: the PLOS One team mapped predicate genealogies across cleared devices generally in 2021, and a study in &lt;em&gt;The Lancet Digital Health&lt;/em&gt; built the predicate networks specifically for AI/ML devices cleared from 2019 to 2021. What those walks found is the part worth carrying to a general reader.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;More than a third of cleared AI/ML devices trace back to a non-AI/ML device in the first generation.&lt;/strong&gt; The equivalence argument crosses the category boundary in a single hop: a system built on a learned model, vouched for by a device that does not learn.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And the ancestors are old.&lt;/strong&gt; In the Lancet study's 2019–2021 cohort, the most recent ancestor that itself carried an AI/ML component dated to 2008 in cardiovascular devices, and to &lt;strong&gt;2001&lt;/strong&gt; in radiology and haematology. A radiology AI cleared in 2021 could sit in a chain whose last AI-bearing ancestor predates AlexNet by eleven years, the deep learning era in its entirety, and every architecture the 2021 device is actually made of. Whatever that 2001 device did, it was not doing what a 2021 convolutional network does. In radiology specifically, the study also found the AI/ML &lt;em&gt;task&lt;/em&gt; changed repeatedly along predicate networks, a drift with its own name in the literature, &quot;predicate creep,&quot; documented in a 2023 PLOS One case study of a robotic surgical system.&lt;/p&gt;
&lt;p&gt;Bound this honestly, because the point is sharper when it is fair. &quot;Substantially equivalent&quot; is a statutory term of art. It does not mean &quot;identical&quot; and never claimed to; it means comparable intended use and characteristics raising no new safety questions, and each individual hop in these chains presumably met that bar. The question the genealogy work raises is not whether anyone broke the rules. It is what a &lt;em&gt;chain of pairwise comparisons&lt;/em&gt; is capable of carrying across twenty years: whether &quot;no new questions, twenty times in a row&quot; can quietly accumulate into a very new answer. Any engineer who has watched a codebase drift one reasonable refactor at a time knows the shape of that question.&lt;/p&gt;
&lt;h3 id=&quot;the-row-is-a-snapshot-of-something-licensed-to-move&quot;&gt;The row is a snapshot of something licensed to move&lt;/h3&gt;
&lt;p&gt;There is a second, quieter way the list undersells what is happening, and most commentary misses it entirely. Roughly 10 percent of 2025 clearances, per secondary summaries, included a Predetermined Change Control Plan (a PCCP). A PCCP lets the manufacturer specify, in advance and with the regulator's sign-off, how the model may be &lt;em&gt;updated after clearance&lt;/em&gt; without filing a new submission.&lt;/p&gt;
&lt;p&gt;Think about what that does to the spreadsheet. The list records a clearance date, one row, one moment. A device with a PCCP is licensed to stop being the exact device that was cleared, within pre-agreed bounds. The row is a photograph of something designed to keep moving after the shutter closed. This is arguably the &lt;em&gt;correct&lt;/em&gt; regulatory answer to the awkward fact that ML systems retrain. The alternative is freezing models in 2019 amber or re-reviewing every weight update. But it changes what a clearance date means, and it means one in ten recent rows is best read the way you read a dependency pinned to a version &lt;em&gt;range&lt;/em&gt; rather than a hash: accurate on the day, guaranteed to drift.&lt;/p&gt;
&lt;h3 id=&quot;the-gate-and-the-outcomes&quot;&gt;The gate and the outcomes&lt;/h3&gt;
&lt;p&gt;Everything so far is about the gate. The list's deepest silence (and the brief's own caution, which deserves to be kept) is that it records nothing about what happened &lt;em&gt;after&lt;/em&gt; the gate. Clearance is not efficacy. A 1,524-row list of authorizations contains zero rows about performance in the field.&lt;/p&gt;
&lt;p&gt;But an outcomes dataset does exist, and it puts a clock on that silence. A study of 950 AI-enabled devices authorized through November 2024 (reported in JAMA-affiliated coverage) found 60 devices associated with 182 recall events. Two findings stand out.&lt;/p&gt;
&lt;p&gt;First, the skew: publicly traded companies made 53.2 percent of the devices and accounted for more than 90 percent of the recall events, a 5.9x association between public-company status and recalls. That runs exactly opposite the intuitive story, in which under-resourced startups cut corners and incumbent giants have quality systems. The coverage also reports that devices lacking clinical validation were recalled more often than those with trials or retrospective studies behind them, a direction I will state without printing the per-segment percentages that circulate, because the denominators in the secondary coverage are murky and the underlying JAMA paper is where those numbers should be read before anyone repeats them.&lt;/p&gt;
&lt;p&gt;Second, the timing, which is the number that closes this essay's argument without overclaiming: roughly &lt;strong&gt;43 percent of those recall events came within one year of authorization&lt;/strong&gt;. That statistic says nothing about whether any individual clearance was wrong. What it says is that the gate and the outcome are separated by months, not decades, and that when the answer-by-analogy misses something, the miss tends to surface fast, in a system the list never records. The spreadsheet's silence about outcomes is a short silence.&lt;/p&gt;
&lt;h3 id=&quot;what-to-do-with-this&quot;&gt;What to do with this&lt;/h3&gt;
&lt;p&gt;For a reader who builds, buys, or signs off on AI systems, medical or otherwise, the list teaches a portable discipline: when someone shows you an approval, ask what the approval &lt;em&gt;is&lt;/em&gt;. Concretely, three questions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Which door?&lt;/strong&gt; &quot;FDA-cleared&quot; spans a 94.6-percent-of-the-time equivalence argument and a from-scratch evidence review, and the phrase does not tell you which you got. Every certification regime you will ever meet (SOC 2, ISO, app-store review) has the same split between demonstrated-directly and inherited-by-analogy. Find out which one you are holding.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What predicate?&lt;/strong&gt; An equivalence argument is an inheritance, and inheritances have ancestors. For a medical device, the immediate predicate is public record even though the full chain is not. For anything else (a model card citing a benchmark, a vendor citing a framework) the same question applies: what, exactly, is this thing claiming to be like, and how old is that thing?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What happens after the gate?&lt;/strong&gt; Is the artifact frozen or licensed to change (the PCCP question), and is anyone measuring outcomes on the other side (the recalls question)? A green light with no post-gate telemetry is a very different object from one with a feedback loop, and the two look identical on a certificate.&lt;/p&gt;
&lt;p&gt;And if you are the data-minded sort: the predicate networks have been walked for 2019 through 2021, and, in everything surveyed for this piece, nobody has published the walk for the 2024 or 2025 cohorts, the two biggest years in the list's history. The documents are public. The chains are sitting there, one hop per PDF, waiting for someone with a scraper and a free weekend. That is not a metaphor for anything. It is just true, and it is the most concrete invitation this spreadsheet extends.&lt;/p&gt;
&lt;p&gt;The list itself will keep doing what it does: recording, one row at a time, a decade of the question &quot;is it safe?&quot; being answered by pointing at a previous answer. It is a useful public artifact, more transparent than what most industries publish about their gates. Just read it as what it is: a record of doors opened, with the doors' reasons filed elsewhere and the rooms beyond them unrecorded.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Sourcing notes: every figure is from the named public sources; none was computed from the raw list, which was not downloaded and row-counted for this piece. A reader needing one authoritative total should count the published list on a named date. The 2001/2008 ancestors and the non-AI-predicate share are the Lancet study's, 2019–2021 cohort only. The PCCP ~10 percent figure is secondary-sourced, unverified against a primary. The per-segment validation-absence percentages circulating in recall coverage were deliberately omitted pending the primary JAMA paper; the recall counts, public-company skew, and ~43 percent first-year figure are as the cited trade coverage reports them.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;sources&quot;&gt;Sources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;FDA, Artificial Intelligence-Enabled Medical Devices list (the dataset under discussion). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fda.gov/medical-devices/software-medical-device-samd/artificial-intelligence-enabled-medical-devices&quot;&gt;https://www.fda.gov/medical-devices/software-medical-device-samd/artificial-intelligence-enabled-medical-devices&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;IntuitionLabs, FDA AI medical device tracker (1,524-entry snapshot, late March 2026; 2015 and 2025 annual counts). &lt;a rel=&quot;nofollow&quot; href=&quot;https://intuitionlabs.ai/articles/fda-ai-medical-device-tracker&quot;&gt;https://intuitionlabs.ai/articles/fda-ai-medical-device-tracker&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Imaging Wire, &quot;AI-enabled medical devices granted FDA marketing authorization,&quot; December 2025 (cumulative and radiology counts through 2025). &lt;a rel=&quot;nofollow&quot; href=&quot;https://theimagingwire.com/2025/12/10/ai-enabled-medical-devices-granted-fda-marketing-authorization/&quot;&gt;https://theimagingwire.com/2025/12/10/ai-enabled-medical-devices-granted-fda-marketing-authorization/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&quot;Machine Learning-Enabled Medical Devices Authorized by the FDA in 2024: Regulatory Characteristics, Predicate Lineage, and Transparency Reporting&quot; (the 94.6%/5.4% 2024 pathway split). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.ncbi.nlm.nih.gov/pmc/articles/PMC12730494/&quot;&gt;https://www.ncbi.nlm.nih.gov/pmc/articles/PMC12730494/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&quot;FDA-cleared artificial intelligence and machine learning-based medical devices and their 510(k) predicate networks,&quot; The Lancet Digital Health (the 2019–2021 predicate networks; the 2001/2008 ancestors; the non-AI predicate share). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.thelancet.com/journals/landig/article/PIIS2589-7500(23)00126-7/fulltext&quot;&gt;https://www.thelancet.com/journals/landig/article/PIIS2589-7500(23)00126-7/fulltext&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&quot;Mapping the genealogy of medical device predicates in the United States,&quot; PLOS One, October 2021 (the one-generation reporting limit, quoted). &lt;a rel=&quot;nofollow&quot; href=&quot;https://journals.plos.org/plosone/article?id=10.1371%2Fjournal.pone.0258153&quot;&gt;https://journals.plos.org/plosone/article?id=10.1371%2Fjournal.pone.0258153&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&quot;Identification of predicate creep under the 510(k) process,&quot; PLOS One, 2023. &lt;a rel=&quot;nofollow&quot; href=&quot;https://journals.plos.org/plosone/article?id=10.1371%2Fjournal.pone.0283442&quot;&gt;https://journals.plos.org/plosone/article?id=10.1371%2Fjournal.pone.0283442&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MedTech Dive, &quot;AI medical devices with no clinical validation tied to more recalls&quot; (the 950-device recalls study coverage). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.medtechdive.com/news/ai-medical-devices-no-validation-more-recalls-jama/758991/&quot;&gt;https://www.medtechdive.com/news/ai-medical-devices-no-validation-more-recalls-jama/758991/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TechTarget, &quot;Public companies linked to 92% of AI medical device recalls&quot; (the public-company skew; the first-year recall timing). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.techtarget.com/healthtechanalytics/news/366630073/Public-companies-linked-to-92-of-AI-medical-device-recalls&quot;&gt;https://www.techtarget.com/healthtechanalytics/news/366630073/Public-companies-linked-to-92-of-AI-medical-device-recalls&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FDA, &quot;Premarket Notification 510(k)&quot; (the substantial-equivalence standard). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fda.gov/medical-devices/premarket-submissions-selecting-and-preparing-correct-submission/premarket-notification-510k&quot;&gt;https://www.fda.gov/medical-devices/premarket-submissions-selecting-and-preparing-correct-submission/premarket-notification-510k&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;where-the-lineage-would-live&quot;&gt;Where the lineage would live&lt;/h3&gt;
&lt;p&gt;The discipline this list teaches is provenance: not &lt;em&gt;is it approved&lt;/em&gt; but &lt;em&gt;what is the approval, what is it inherited from, and how old is that ancestor&lt;/em&gt;. The FDA's own record shows each device's immediate parent and never its lineage, so the chain has to be reconstructed by hand, one hop per document.&lt;/p&gt;
&lt;p&gt;Chain of Consciousness keeps that record for agent decisions: what was read, in what order, to arrive at what was claimed. The ancestry is written down rather than inferred.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pip install chain-of-consciousness&lt;/code&gt; and &lt;code&gt;npm install chain-of-consciousness&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/hosted-coc/&quot;&gt;Hosted Chain of Consciousness&lt;/a&gt;&lt;/p&gt;
</description>
<category>Data &amp; AI</category>
<guid isPermaLink="true">https://coderlegion.com/31064/a-thousand-cleared-ai-devices-what-the-fdas-own-list-says-about-how-ai-gets-approved</guid>
<pubDate>Wed, 14 Oct 2026 12:14:24 +0000</pubDate>
</item>
<item>
<title>Introducing Native UUIDs in ThingsDB</title>
<link>https://coderlegion.com/30409/introducing-native-uuids-in-thingsdb</link>
<description>&lt;p&gt;In the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/thingsdb/ThingsDB/releases/latest&quot;&gt;latest release&lt;/a&gt;, ThingsDB introduces native support for UUIDs alongside a powerful new typed dictionary pattern (&lt;code&gt;dict&amp;lt;uuid:Type&amp;gt;&lt;/code&gt;) that simplifies high-performance key-value lookups.&lt;/p&gt;
&lt;p&gt;While ThingsDB relies on auto-incrementing integer IDs under the hood for maximum internal efficiency, integers aren't always ideal for public application interfaces. Exposing sequential integer IDs can expose business metrics or open your API to enumeration attacks. Native UUID support solves this without sacrificing speed or developer experience.&lt;/p&gt;
&lt;h3 id=&quot;how-thingsdb-handles-uuids&quot;&gt;How ThingsDB Handles UUIDs&lt;/h3&gt;
&lt;p&gt;ThingsDB uses &lt;strong&gt;UUID version 7&lt;/strong&gt; for its native UUID implementation. Because UUID v7 embeds a Unix timestamp in its leading bits, generated IDs remain time-ordered &lt;em&gt;(chronologically sortable)&lt;/em&gt; while ensuring global uniqueness across your cluster:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sequential Efficiency:&lt;/strong&gt; Since UUIDs sort naturally by creation time, database indexes stay performant without the index fragmentation typically caused by purely random UUIDs &lt;em&gt;(like UUID v4)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Guaranteed Uniqueness:&lt;/strong&gt; Generation accounts for node identification, precise millisecond timestamps, and cryptographically sound randomness, so no two nodes will ever generate colliding UUIDs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Internally, ThingsDB processes and stores UUIDs as efficient 128-bit integers. However, when interacting with the API or serializing data to JSON, ThingsDB exposes them as standard, lower-case hyphenated strings:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;00f3c1b0-7d12-7000-8b1e-213456789abc&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;building-a-user-management-system-with-uuids&quot;&gt;Building a User Management System with UUIDs&lt;/h3&gt;
&lt;p&gt;Let's walk through a practical example: creating a custom &lt;code&gt;User&lt;/code&gt; type that uses UUIDs instead of default ThingsDB integer IDs.&lt;/p&gt;
&lt;h4 id=&quot;1-define-the-user-type&quot;&gt;1. Define the User Type&lt;/h4&gt;
&lt;p&gt;We'll start by defining the &lt;code&gt;User&lt;/code&gt; type with two properties: a &lt;code&gt;uuid&lt;/code&gt; field named &lt;code&gt;id&lt;/code&gt; and a string &lt;code&gt;name&lt;/code&gt;. We'll also pass the &lt;code&gt;HID&lt;/code&gt; flag to hide ThingsDB's default internal integer ID from API responses.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Define the User type.
// We use the `HID` flag to suppress the default internal integer ID.
set_type('User', {
    id: 'uuid',
    name: 'str',
}, HID);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-set-up-collection-storage&quot;&gt;2. Set Up Collection Storage&lt;/h4&gt;
&lt;p&gt;Instead of using a generic dictionary, we can define a dedicated root &lt;code&gt;App&lt;/code&gt; type to anchor our collection data. We'll use the new &lt;code&gt;dict&amp;lt;uuid:User&amp;gt;&lt;/code&gt; type constraint to ensure the dictionary map strictly pairs uuid keys with &lt;code&gt;User&lt;/code&gt; instances.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Define the App root structure with a typed user lookup dictionary
set_type('App', {
    users: 'dict&amp;lt;uuid:User&amp;gt;',
}, HID);

// Convert the collection to the App type (automatically initializes `users`)
.to_type('App');
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-implement-procedures-for-crud-operations&quot;&gt;3. Implement Procedures for CRUD Operations&lt;/h4&gt;
&lt;p&gt;To maintain clean architecture, we'll write reusable procedures.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;By convention, using CamelCase for custom procedures helps distinguish them from built-in ThingsDB functions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5 id=&quot;adding-users-newuser-&quot;&gt;Adding Users (&lt;code&gt;NewUser&lt;/code&gt;)&lt;/h5&gt;
&lt;p&gt;Creating a user is straightforward. ThingsDB automatically generates a valid UUID if one isn't explicitly provided in the payload:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;new_procedure('NewUser', |object| {
    user = User(object);
    .users[user.id] = user;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run this procedure, use the &lt;code&gt;!&lt;/code&gt; operator &lt;em&gt;(or &lt;code&gt;wse()&lt;/code&gt;)&lt;/em&gt; to tell ThingsDB that the call produces side effects:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;NewUser({
    name: 'Alice',
})!;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Response:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
    &quot;id&quot;: &quot;c9bf9e57-1685-4c89-bafb-ff5af830be8a&quot;,
    &quot;name&quot;: &quot;Alice&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you already have a UUID generated on the client side, you can pass it directly in the payload (&lt;code&gt;{id: &quot;...&quot;, name: &quot;Alice&quot;}&lt;/code&gt;), and ThingsDB will parse and store it automatically.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5 id=&quot;fetching-users-getuserbyid-&quot;&gt;Fetching Users (&lt;code&gt;GetUserById&lt;/code&gt;)&lt;/h5&gt;
&lt;p&gt;While you can iterate through dictionary values, accessing users directly by key using the new &lt;code&gt;uuid()&lt;/code&gt; cast function offers O(1) fast lookup performance:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;new_procedure('GetUserById', |id| .users[uuid(id)]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, querying a user by string ID is clean and efficient:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;GetUserById('c9bf9e57-1685-4c89-bafb-ff5af830be8a');
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;calling-procedures-from-your-application&quot;&gt;Calling Procedures from Your Application&lt;/h3&gt;
&lt;p&gt;Once your procedures are defined, you can invoke them directly from your application using any native client library or standard HTTP requests.&lt;/p&gt;
&lt;h4 id=&quot;using-native-sdks-python-go-c-java-javascript-etc-&quot;&gt;Using Native SDKs (Python, Go, C#, Java, JavaScript etc.)&lt;/h4&gt;
&lt;p&gt;Native clients make procedure execution seamless. For example, using the &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.thingsdb.io/v1/connect/python/&quot;&gt;Python client&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Execute the GetUserById procedure using the native client
user = await client.run('GetUserById', 'c9bf9e57-1685-4c89-bafb-ff5af830be8a')
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;using-the-rest-api&quot;&gt;Using the REST API&lt;/h4&gt;
&lt;p&gt;ThingsDB also exposes procedure execution over HTTP POST requests, making it easy to integrate with any language or web service:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl --location --request POST 'http://&amp;lt;HOSTNAME:PORT&amp;gt;/collection/&amp;lt;COLLECTION_NAME&amp;gt;' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer &amp;lt;TOKEN&amp;gt;' \
  --data-raw '{
    &quot;type&quot;: &quot;run&quot;,
    &quot;name&quot;: &quot;GetUserById&quot;,
    &quot;args&quot;: [&quot;c9bf9e57-1685-4c89-bafb-ff5af830be8a&quot;]
  }'
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;With native 128-bit UUID support and typed key-value dictionaries, ThingsDB gives you the security and convenience of string UUIDs on the outside, paired with the storage performance of fast integer lookups on the inside.&lt;/p&gt;
&lt;p&gt;Give it a try in the latest release and let us know what you think!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30409/introducing-native-uuids-in-thingsdb</guid>
<pubDate>Fri, 09 Oct 2026 06:28:57 +0000</pubDate>
</item>
<item>
<title># Power BI Data Modelling: A Great Path to Great Analysis</title>
<link>https://coderlegion.com/30210/power-bi-data-modelling-a-great-path-to-great-analysis</link>
<description>&lt;h2 id=&quot;data-modeling-in-power-bi&quot;&gt;Data Modeling in Power BI&lt;/h2&gt;
&lt;h3 id=&quot;introduction-1&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;At a time when data is rapidly generated by different activities ranging from experiments to business, more often do we get data in different formats and structures. The data may or may not be related depending on the source. Related data in different formats and structure need to be re-organized to establish existing relationships for accurate analysis and reporting. To establish data model relationships and properties we need advanced tools that can connect to these sources. Then load the different datasets from source, and perform modelling to connect the links.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Data Modeling&lt;/em&gt; therefore, becomes very important. &lt;em&gt;Data Modeling is the process of defining the data structure, properties, and relationships within a &lt;em&gt;Data Model&lt;/em&gt;.&lt;/em&gt; Through organizing tables and columns, defining relationships between tables in the model, setting cardinality and cross-filter direction we achieve the best model which supports accurate calculations and reporting.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A Data Model&lt;/em&gt; is the logical representation of how data is structured and related within the data model.&lt;/p&gt;
&lt;p&gt;Before modeling ensure that your data is clean and up to date, ready for modeling.&lt;/p&gt;
&lt;h5 id=&quot;why-data-modeling&quot;&gt;Why Data Modeling&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Improved Performance and Scalability&lt;/em&gt; - join optimization, filters, and aggregations lead to a good model that is efficient and reliable. However, poorly designed models with complex relationships and redundant records cause delays, making queries slow.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Accuracy and Consistency&lt;/em&gt; - Guarantees the accuracy and consistency of the data used, making it reliable in reporting and drawing precise  insights and improved decision making.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Supports Data Exploration&lt;/em&gt; - Users can use hierarchies and drill-down paths to support efficient data exploration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Ease of Maintenance&lt;/em&gt; - Any changes in one model or report are reflected everywhere in real time enabling reusability and scalability, reducing the effort required to maintain reports and models as the business grows.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;components-of-power-bi-data-model&quot;&gt;Components of Power BI Data Model&lt;/h3&gt;
&lt;p&gt;a. &lt;em&gt;Tables and Sources&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Tables&lt;/em&gt; are the raw building blocks of data models. They contain fields and records of particular data from different sources.&lt;br&gt;
&lt;em&gt;Sources,&lt;/em&gt; where we get our data from can vary depending on the context. We can get our data from SQL Database servers, Excel files, Data warehouse, SharePoint, and Power BI Dataflows.&lt;/p&gt;
&lt;h5 id=&quot;designing-source-tables&quot;&gt;Designing Source Tables&lt;/h5&gt;
&lt;p&gt;i. &lt;em&gt;Fact Tables&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Fact tables contain quantitative data, numerical data that can be aggregated and analyzed. Examples the &lt;em&gt;Sales, Orders, or Invoices.&lt;/em&gt; This information is about specific events, business transaction, or measurements at the core of the data model.&lt;/p&gt;
&lt;p&gt;Fact tables keeps one record for each transaction or event, includes only keys and metrics, and they avoid storing descriptive attributes.&lt;/p&gt;
&lt;p&gt;Fact tables contain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Foreign Key(FK)&lt;/em&gt; - a link to the Dimension table i.e. DateKey, ProductKey, e.t.c.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Measures(Numeric values)&lt;/em&gt; - Quantitative data you can aggregate. i.e. Quantity, UnitPrice, TotalAmount etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Degenerate dimensions&lt;/em&gt; - Descriptive data that does not warrant its dimension table. i.e. OrderNumber, InvoiceNumber.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ii. &lt;em&gt;Dimension Tables&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Dimension tables provide context or additional details to the data in &lt;em&gt;Fact Tables&lt;/em&gt;. They contains fewer rows while fact tables can contain a large number of rows and they are on growing.&lt;/p&gt;
&lt;p&gt;Relationship are used to establish links and connections between the fact and dimension tables in Power BI.&lt;/p&gt;
&lt;p&gt;Dimension table contain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Primary Key(PK)&lt;/em&gt; - Unique identifier e.g.  &lt;code&gt;ProductID&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Descriptive attributes&lt;/em&gt; - Text, categories, hierarchies e.g. &lt;code&gt;ProductName&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Hierarchy Columns&lt;/em&gt; - Columns that form drill-down paths e.g. Country -&amp;gt; City -&amp;gt; Town.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Natural key&lt;/em&gt; - original identifier from the source system e.g. &lt;code&gt;ProductCode&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;iii. &lt;em&gt;Reference and Lookup Tables&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;They are small but critical tables in supporting the integrity and interpretation of data. They support additional mappings  or categorizations. Example: DiscountTypes or StatusCodes.&lt;/p&gt;
&lt;h5 id=&quot;loading-data&quot;&gt;Loading Data&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;Open Power BI&lt;/li&gt;
&lt;li&gt;On the Ribbon &lt;code&gt;Home&lt;/code&gt; tab, Click on &lt;code&gt;Get Data&lt;/code&gt; from the &lt;em&gt;Data&lt;/em&gt; group option.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/u4k0yh2aocmcpqkzobxw.png&quot; alt=&quot;Get Data Icon&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;From the drop down list, choose the source of your data. i.e. &lt;code&gt;Text/CSV&lt;/code&gt; if your data is text or comma separated file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/e7iqcv5qwsoa15ffkif8.png&quot; alt=&quot;Data Source&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Choose the path to your file i.e. &lt;code&gt;C:/Users/User/Downloads/file.csv&lt;/code&gt; and click on &lt;code&gt;Open&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click on &lt;code&gt;Connect&lt;/code&gt; on the Power Query editor window.&lt;/li&gt;
&lt;li&gt;Your data is extracted to Power Query for cleaning. Click on &lt;code&gt;Transform - Data&lt;/code&gt; to clean explore and clean it on the Power Query editor.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/spze6r2i7ekktnp3xsy5.png&quot; alt=&quot;Query Editor Window&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/3wsmqrfwt2dov26sik8o.png&quot; alt=&quot;Cleaning Window&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Finally, on the Power Query Editor Window Ribbon, Click on &lt;code&gt;Close &amp;amp; Apply&lt;/code&gt; from the &lt;code&gt;Home&lt;/code&gt; tab.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/vgs0p47x4kyktm1t4i4p.png&quot; alt=&quot;Close and Apply Button&quot;&gt;&lt;/p&gt;
&lt;p&gt;Changes are loaded and applied to Power BI. We will load all our &lt;a rel=&quot;nofollow&quot; href=&quot;https://mavenanalytics.io/data-playground/toy-store-e-commerce-database&quot;&gt;Toy Store E-Commerce Database&lt;/a&gt; data set for modeling using the procedure above. The data is already clean, we are going to use it for demonstrating relationships in data modeling.&lt;/p&gt;
&lt;p&gt;b. &lt;em&gt;Relationships&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Relationships are an important aspect of data modeling in Power BI. Relationships are for establishing connections and links between tables of a data model. They allow related tables to communicate with each other. They are the link between &lt;em&gt;Fact Tables&lt;/em&gt; and &lt;em&gt;Dimension Tables&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Relationships determine how data in the model is filtered, aggregated, combined, and visualized. They're critical to the accuracy and performance of queries in Power BI.&lt;/p&gt;
&lt;h6 id=&quot;cardinality&quot;&gt;Cardinality&lt;/h6&gt;
&lt;p&gt;Cardinality type defines each relationship in a model. &lt;em&gt;Cardinality&lt;/em&gt; refers to the number of unique values in one table related to the number of unique values in another.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Types of Cardinality in Relationships&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;One-to-One(1:1)&lt;/em&gt; - Every single record in one table is related to another single record in another table. Commonly used when splitting a large table for performance and security reasons.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;One-to-Many(1:N)&lt;/em&gt; - Refers to when a single record in one table is related to many records in another table(s). Very common type of relationship in Power BI models. The &lt;em&gt;Fact&lt;/em&gt; table is on the &lt;em&gt;Many&lt;/em&gt; side while the &lt;em&gt;Dimension&lt;/em&gt; table is on the &lt;em&gt;One&lt;/em&gt; side when linking &lt;em&gt;Fact&lt;/em&gt; tables and &lt;em&gt;Dimension&lt;/em&gt; tables.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Many-to-Many(N:N)&lt;/em&gt; - Refers to when many records in the first table are related to many other records in the second table.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h6 id=&quot;cross-filter-directions&quot;&gt;Cross Filter Directions&lt;/h6&gt;
&lt;p&gt;Used to determine the direction which filters propagate.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Single&lt;/em&gt; - When applied, only one of the tables in the relationship can filter data across the other table. Filtering data on the other table does not affect the data shown on the first table.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Bi-Directional&lt;/em&gt; - When applied, data can be filtered on either direction between the tables. Filtering data in one table affects data shown on the other table and vice versa. It is less often used and can impact performance negatively and create ambiguous filters.&lt;/p&gt;
&lt;p&gt;Cross-filter options are largely dependent on the cardinality type.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Cardinality &lt;/th&gt;&lt;th&gt;Cross-Filter Options&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1:1 &lt;/td&gt;&lt;td&gt;Bi-Directional&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1:* &lt;/td&gt;&lt;td&gt;Single or Bi-Directional&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;em&gt;:&lt;/em&gt; &lt;/td&gt;&lt;td&gt;Single or Bi-Directional&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;Table 1: A table showing cardinality and their cross-filter options.&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;creating-a-relationship&quot;&gt;Creating a Relationship&lt;/h4&gt;
&lt;p&gt;To create relationships, first you need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Identify the matching columns(keys).&lt;/li&gt;
&lt;li&gt;Choose the right cardinality.&lt;/li&gt;
&lt;li&gt;Decide on the Filter direction.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;the-model-view-interface&quot;&gt;The Model View Interface&lt;/h5&gt;
&lt;p&gt;To manage relationships in Power BI Desktop:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click the Model icon in the left sidebar (the icon with three connected boxes)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/4fw45c9o13jvbf7fimho.png&quot; alt=&quot;Model&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You'll see all your tables displayed as boxes with their columns listed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/3jhqw6m20jhiip0t3mn9.png&quot; alt=&quot;Tables in the Model&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Lines between tables represent relationships&lt;/li&gt;
&lt;li&gt;The line style indicates cardinality: 1 on one end and * on the many end&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;auto-detect-relationships&quot;&gt;Auto-Detect Relationships&lt;/h5&gt;
&lt;p&gt;When you load tables, Power BI automatically recognizes columns with same data type and name and creates relationships. However, you shoul always create relationships manually or review those created automatically. &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Just like in our case, Power BI automatically creates relationships between tables in our model. We will review them.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;When auto-detect works well:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Column names match exactly (e.g., ProductID in both tables)&lt;/li&gt;
&lt;li&gt;Data types are compatible&lt;/li&gt;
&lt;li&gt;Values actually correspond between the tables&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;creating-relationships-manually&quot;&gt;Creating Relationships Manually&lt;/h5&gt;
&lt;p&gt; Using the Managing Relationship Dialog&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On the &lt;code&gt;Home&lt;/code&gt; tab  → &lt;code&gt;Manage Relationships&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/mqre7817gnrqv31vn7ng.png&quot; alt=&quot;Manage Relationships&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click &lt;code&gt;New&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/k5owghexz7iiyclqvdli.png&quot; alt=&quot;New Relationship&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select the first table and column, &lt;code&gt;From Table&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Select the second table and column, &lt;code&gt;To Table&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Configure cardinality and cross-filter direction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/specifytables.png&quot; alt=&quot;Configure Relationship Tables&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click &lt;code&gt;Save&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h6 id=&quot;step-by-step-for-creating-relationships-for-our-toy-store-e-commerce-database&quot;&gt;Step by Step for Creating Relationships for our Toy Store E-Commerce Database&lt;/h6&gt;
&lt;ol&gt;
&lt;li&gt;Load all tables: &lt;pre&gt;&lt;code&gt; ```
     products
     orders
     order_items
     order_item_refunds
     website_sessions
     website_pageviews
 ````
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Open Model view and delete any autodetected relationship.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;To delete a relationship:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In Model view, right-click on the line joining the &lt;code&gt;products&lt;/code&gt; and &lt;code&gt;order_items&lt;/code&gt; tables&lt;/li&gt;
&lt;li&gt;From the options displayed, Click on &lt;code&gt;Delete&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/i7ete2c79b1mp6sm1od3.png&quot; alt=&quot;Delete Relationship&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Save your work&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Create the following relationships:&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;From Fact Table &lt;/th&gt;&lt;th&gt;To Dimension Table &lt;/th&gt;&lt;th&gt;Cardinality &lt;/th&gt;&lt;th&gt;Cross-Filter&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;products[product_id] &lt;/td&gt;&lt;td&gt;order_items[product_id] &lt;/td&gt;&lt;td&gt;One-to-Many &lt;/td&gt;&lt;td&gt;Single&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;orders[order_id] &lt;/td&gt;&lt;td&gt;order_items[order_id] &lt;/td&gt;&lt;td&gt;One-to-Many &lt;/td&gt;&lt;td&gt;Single&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;orders[order_id] &lt;/td&gt;&lt;td&gt;order_item_refunds[order_id] &lt;/td&gt;&lt;td&gt;One-to-Many &lt;/td&gt;&lt;td&gt;Single&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;website_sessions[website_session_id] &lt;/td&gt;&lt;td&gt;orders[website_session_id] &lt;/td&gt;&lt;td&gt;One-to-One &lt;/td&gt;&lt;td&gt;Bi-Direction&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;website_sessions[website_session_id] &lt;/td&gt;&lt;td&gt;website_pageviews[website_session_id] &lt;/td&gt;&lt;td&gt;One-to-Many &lt;/td&gt;&lt;td&gt;Single&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;order_items[order_item_id] &lt;/td&gt;&lt;td&gt;order_item_refunds[order_item_id] &lt;/td&gt;&lt;td&gt;One-to-one &lt;/td&gt;&lt;td&gt;Bi-Direction&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;Table 2: Table showing relationships to be created.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Creating a relationship between the &lt;code&gt;products&lt;/code&gt; and &lt;code&gt;order_items&lt;/code&gt; tables&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On the &lt;code&gt;Home&lt;/code&gt; tab  → &lt;code&gt;Manage Relationships&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;New&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;products&lt;/code&gt; table and specify the column &lt;code&gt;product_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;order_items&lt;/code&gt; table and specify the column &lt;code&gt;product_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Set cardinality to &lt;code&gt;One-to-Many&lt;/code&gt; and the cross-filter as &lt;code&gt;Single&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/tikqfcb3k8mogoo03723.png&quot; alt=&quot;Specify Table Configurations&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click &lt;code&gt;Save&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/qdh3npai9fx6pj2b4jk5.png&quot; alt=&quot;Established Relationship&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Complete creating all the other relationships following the procedure. You will get something similar to:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/fbqtjzxxq3zha4emmxfp.png&quot; alt=&quot;All Relationships&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Verify by checking that filters from any dimension table correctly filter the fact table.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;conclusion-2&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Data modeling is very crucial to building powerful reports and visualizations. It is key to great analysis because it establishes relationships, links, and connections existing in a data model making it wholesome and complete. Further, it improves query performance increasing efficiency and reliability of queries. Correct data modeling can lead to accurate reporting and result reliability, thus informed decision making. To view the complete model and data for your practice on GitHub visit &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MakoriNyachaki/Toy-Store-E-Commerce&quot;&gt;Toy Store E-Commerce&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30210/power-bi-data-modelling-a-great-path-to-great-analysis</guid>
<pubDate>Thu, 08 Oct 2026 03:59:58 +0000</pubDate>
</item>
<item>
<title>The Forgetting Curve Everyone Cites Is Ebbinghaus's Own Column, Relabeled. The 90% Is Not in It.</title>
<link>https://coderlegion.com/30006/the-forgetting-curve-everyone-cites-is-ebbinghauss-own-column-relabeled-the-90-is-not-in</link>
<description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/the-forgetting-curve-everyone-cites-is-ebbinghaus-s-own-column-relabeled-the-90-is-not-in-it/&quot;&gt;vibeagentmaking.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The &quot;half in an hour, 70% in a day&quot; figures that training blogs attribute to Ebbinghaus are his own column IV, a share of relearning time for lists of nonsense syllables, with the unit dropped. The 90% is not in his table; an exponential fits it five times worse than his 1885 formula; two replications confirm the shape.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;On June 8, 2026, MeltingSpot's blog published a long post on the cost of forgetting in corporate training. Its first section, &quot;What the Ebbinghaus forgetting curve actually tells us,&quot; reports what Hermann Ebbinghaus found: &quot;roughly 56% is forgotten within one hour of learning, about 66% within one day, and around 75% within six days.&quot; Those figures are close to numbers Ebbinghaus printed. Two paragraphs later, in the same section, the post says that if a team finishes a Monday morning training and gets no review for the rest of the week, &quot;cognitive science predicts that 90% of the material will be gone by Friday.&quot;&lt;/p&gt;
&lt;p&gt;So 75% at six days became 90% at four, two paragraphs apart. The post then puts a price on it. Citing the Association for Talent Development's estimate that US organizations spend an average of $1,254 per employee per year on training, it says: &quot;If 90% of that training content is not retained past the first week, the effective return on investment is not just low. It is catastrophic.&quot; The worked figure that follows is &quot;roughly $940&quot; wasted per employee. That is 75% of $1,254, the six-day number the post gave in its first section. Ninety percent would be $1,129.&lt;/p&gt;
&lt;p&gt;The same figures run through the training industry's explainers. TalentLMS's blog says Ebbinghaus &quot;found that within an hour of learning new information people tend to forget up to 50% of it. Within 24 hours, this can increase to 70%. By the end of the week, people tend to retain only about 25% of what they've learned.&quot; Further down, citing &quot;studies&quot; that illustrate the curve, it says people &quot;tend to forget up to 90% of what they've learned within a month.&quot; The post dates from March 2023; the page was last modified on September 21, 2026. Today's Class says &quot;on average we forget 50% of new information within an hour, and about 70% within one day,&quot; and &quot;Within 30 days, we forget up to 90% of what we learned.&quot; TrainMeUK's headline from December 2025 is &quot;Why Employees Forget 70% of Training in 24 Hours (The Ebbinghaus Effect),&quot; and its introduction calls the forgetting &quot;a biology problem.&quot;&lt;/p&gt;
&lt;p&gt;We published a version of it too. An essay on this site in April said: &quot;Hermann Ebbinghaus established the lower bound in 1885. Without reinforcement, humans lose roughly half of newly learned information within an hour and most of it within a week.&quot; This piece corrects that sentence, and the one after it, which went further.&lt;/p&gt;
&lt;h3 id=&quot;what-he-measured&quot;&gt;What he measured&lt;/h3&gt;
&lt;p&gt;Ebbinghaus was his own and only subject. The seventh chapter of his book &lt;em&gt;Memory&lt;/em&gt;, in the 1913 English translation by Henry Ruger and Clara Bussenius, gives the design: &quot;The investigations in question fell in the year 1879-80 and comprised 163 double tests.&quot; Each double test meant learning eight series of 13 nonsense syllables (six, in 38 of the tests) until he could recite each series twice without error, then relearning them to the same standard after a fixed interval. He was 29.&lt;/p&gt;
&lt;p&gt;He did not count the syllables he could still recall. He timed the relearning and took the time saved, as a share of the original learning time, as his measure: &quot;This saving in work is each time the measure for the amount remembered at the end of the interval.&quot; If relearning a list took 60% as long as learning it, the saving was 40%.&lt;/p&gt;
&lt;p&gt;He was also careful about whom the results described. Early in his discussion of them he writes that &quot;it would be just as reasonable to be surprised at this initial rapidity and later slowness as they come to light here under the definite conditions of our experiment for a certain individual, and for a series of 13 syllables. One hour after the end of the learning, the forgetting had already progressed so far that one half the amount of the original work had to be expended before the series could be reproduced again; after 8 hours the work to be made up amounted to two thirds of the first effort.&quot;&lt;/p&gt;
&lt;p&gt;That is where &quot;half in an hour&quot; comes from. Its unit is work.&lt;/p&gt;
&lt;h3 id=&quot;the-table&quot;&gt;The table&lt;/h3&gt;
&lt;p&gt;The summary table that closes Section 28 of the chapter has seven rows. Its second column is headed &quot;So much of the series learned was retained that in relearning a saving of Q% of the time of original learning was made.&quot; Its fourth column is headed &quot;The amount forgotten was thus equivalent to v% of the original in terms of time of learning,&quot; and every entry in it is 100 minus the second. The formula table in Section 29 gives the intervals in minutes, and they are less round than the summary suggests: the &quot;1 hour&quot; row was 64 minutes, and the &quot;8.8 hours&quot; row was 526.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Interval &lt;/th&gt;&lt;th&gt;Saving (column II) &lt;/th&gt;&lt;th&gt;Amount forgotten &quot;in terms of time of learning&quot; (column IV)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;20 minutes &lt;/td&gt;&lt;td&gt;58.2% &lt;/td&gt;&lt;td&gt;41.8%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;64 minutes &lt;/td&gt;&lt;td&gt;44.2% &lt;/td&gt;&lt;td&gt;55.8%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;8.8 hours &lt;/td&gt;&lt;td&gt;35.8% &lt;/td&gt;&lt;td&gt;64.2%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1 day &lt;/td&gt;&lt;td&gt;33.7% &lt;/td&gt;&lt;td&gt;66.3%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2 days &lt;/td&gt;&lt;td&gt;27.8% &lt;/td&gt;&lt;td&gt;72.2%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;6 days &lt;/td&gt;&lt;td&gt;25.4% &lt;/td&gt;&lt;td&gt;74.6%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;31 days &lt;/td&gt;&lt;td&gt;21.1% &lt;/td&gt;&lt;td&gt;78.9%&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Set the retellings beside it and most of their numbers turn out to be his. &quot;Half in an hour&quot; is 55.8% in column IV. &quot;Seventy percent in a day&quot; is 66.3%, rounded up. MeltingSpot's 56, 66 and 75 are column IV to the nearest point. TalentLMS's &quot;about 25%&quot; retained by the end of the week is column II at six days, 25.4%, copied correctly. Ebbinghaus made the conversion from savings to &quot;forgotten&quot; himself, in print.&lt;/p&gt;
&lt;p&gt;What changed on the way is the thing the percentage is a percentage of. Column IV is the share of the original learning time that had to be spent again to relearn a list of nonsense syllables. The retellings make it a share of &quot;new information,&quot; of &quot;training content,&quot; of &quot;what they've learned.&quot; A saving of 44.2% after an hour does not mean 55.8% of anything was lost. It means relearning took 55.8% as long as learning had.&lt;/p&gt;
&lt;p&gt;Tom McDowall's critique in his Instructional Design Tips newsletter (November 2025) makes the savings point well, and says &quot;you cannot legitimately convert savings scores into 'percentage forgotten.'&quot; Ebbinghaus did convert them. He printed the result as column IV, with the unit in the heading. What later writers dropped was the unit.&lt;/p&gt;
&lt;p&gt;The 90% has no source in the table. The largest value in column IV is 78.9%, at 31 days. At six days it is 74.6%. MeltingSpot's Friday, four days after a Monday session, falls between the two-day row (72.2%) and the six-day row (74.6%). TrainMeUK's &quot;80% within a month&quot; is near his 78.9%. The &quot;up to 90%&quot; within a month on TalentLMS and Today's Class is 11 points past it.&lt;/p&gt;
&lt;h3 id=&quot;the-shape&quot;&gt;The shape&lt;/h3&gt;
&lt;p&gt;The retellings also name a shape. MeltingSpot: &quot;memory of new material follows an exponential decay curve.&quot; Today's Class: &quot;the percentage of information retained declined exponentially over time without reinforcement.&quot; McDowall's critique calls it &quot;Ebbinghaus's exponential decline curve&quot; too. An exponential loses a fixed fraction of what remains in each equal stretch of time.&lt;/p&gt;
&lt;p&gt;We fitted that shape and two others to his seven savings values by least squares, with time in minutes. His own formula from 1885, with constants he set &quot;with merely approximate estimates, not involving exact calculation by the method of least squares,&quot; misses his data by 1.73 percentage points, root mean square. A power law misses by 1.91. The best exponential misses by 9.10, more than five times the error of his hand-set formula.&lt;/p&gt;
&lt;p&gt;The exponential cannot follow a curve that falls fast and then nearly stops. The best one starts at 40% saved and stays close to it through the first day: 39.9% at 20 minutes, where he measured 58.2%, and 38.8% at one day, where he measured 33.7%. It is too high at a week (32.4%, against 24.5% from his formula) and too low at a month (16.3%, against 21.3%). His own numbers drop 24.5 points between 20 minutes and one day, and only 4.3 points between the sixth day and the thirty-first. He named the pattern himself: &quot;this initial rapidity and later slowness.&quot;&lt;/p&gt;
&lt;p&gt;A 1996 survey points the same way. David Rubin and Amy Wenzel assembled 210 published data sets and fitted each &quot;to 105 different 2-parameter functions.&quot; Their summary: &quot;The best fits were to the logarithmic function, the power function, the exponential in the square root of time, and the hyperbola in the square root of time.&quot; The plain exponential is not among them.&lt;/p&gt;
&lt;h3 id=&quot;three-people-who-ran-it-again&quot;&gt;Three people who ran it again&lt;/h3&gt;
&lt;p&gt;One subject invites the obvious question, whether his memory was unusual. In 2015 Jaap Murre and Joeri Dros of the University of Amsterdam repeated the experiment with Dros, then 22, as the only subject. &quot;One subject spent 70 hours learning lists and relearning them,&quot; at Ebbinghaus's intervals, and &quot;The results are similar to Ebbinghaus' original data.&quot; They set their results beside a German replication from 1991 by Heller, Mack and Seitz, with two subjects, which they describe as published only in German, without an English abstract, and never cited in international journals in English.&lt;/p&gt;
&lt;p&gt;Their comparison table, in savings:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Interval &lt;/th&gt;&lt;th&gt;Ebbinghaus (1879–80) &lt;/th&gt;&lt;th&gt;Mack (1991) &lt;/th&gt;&lt;th&gt;Seitz (1991) &lt;/th&gt;&lt;th&gt;Dros (2015)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;20 minutes &lt;/td&gt;&lt;td&gt;0.582 &lt;/td&gt;&lt;td&gt;0.544 &lt;/td&gt;&lt;td&gt;0.442 &lt;/td&gt;&lt;td&gt;0.472&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1 hour &lt;/td&gt;&lt;td&gt;0.442 &lt;/td&gt;&lt;td&gt;0.432 &lt;/td&gt;&lt;td&gt;0.325 &lt;/td&gt;&lt;td&gt;0.373&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;9 hours &lt;/td&gt;&lt;td&gt;0.358 &lt;/td&gt;&lt;td&gt;0.285 &lt;/td&gt;&lt;td&gt;0.270 &lt;/td&gt;&lt;td&gt;0.276&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1 day &lt;/td&gt;&lt;td&gt;0.337 &lt;/td&gt;&lt;td&gt;0.316 &lt;/td&gt;&lt;td&gt;0.270 &lt;/td&gt;&lt;td&gt;0.317&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2 days &lt;/td&gt;&lt;td&gt;0.278 &lt;/td&gt;&lt;td&gt;0.365 &lt;/td&gt;&lt;td&gt;0.286 &lt;/td&gt;&lt;td&gt;0.230&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;6 days &lt;/td&gt;&lt;td&gt;0.254 &lt;/td&gt;&lt;td&gt;0.309 &lt;/td&gt;&lt;td&gt;0.205 &lt;/td&gt;&lt;td&gt;0.168&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;31 days &lt;/td&gt;&lt;td&gt;0.211 &lt;/td&gt;&lt;td&gt;0.258 &lt;/td&gt;&lt;td&gt;0.201 &lt;/td&gt;&lt;td&gt;0.041&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The shape holds for all four: a fast early drop, then a long shallow slope. At six days the largest loss in column-IV terms is Dros's, 83.2%. At 31 days, Mack is at 74.2% and Seitz at 79.9%, on either side of Ebbinghaus's 78.9%.&lt;/p&gt;
&lt;p&gt;Dros at 31 days is the one number here that cuts against this essay. His saving of 0.041 is 95.9% in column-IV terms, past 90%. The authors treat it with care. His learning time rose by an average of 2.67 seconds a day per list over the 75 days of the experiment, which mostly affects the longest interval, and with a correction for that drift &quot;the corrected savings measure would be 0.137 for the 31 day interval instead of 0.0410,&quot; which is 86.3%. That, they add, &quot;is still well below the values for the three others.&quot; The Dros column above matches savings computed from the repetition counts in the paper's Table 1 at all seven intervals; computed from the seconds in its list-by-list Table 2, the 31-day saving is 0.090, or 91.0%. So there is a 90% in this literature: one subject, at one month, before the authors' correction for drift.&lt;/p&gt;
&lt;p&gt;The replication also found something Ebbinghaus nearly discarded. In three of the four columns the one-day saving is at least as high as the nine-hour saving, which a curve that only runs downhill does not allow. The authors conclude that the curve &quot;is not completely smooth but most probably shows a jump upwards starting at the 24 hour data point,&quot; and point to sleep: &quot;The current body of research on sleep and memory would predict such a boost after one or two nights and attribute it to sleep, though for this particular type of experiments this has to established more firmly in further experiments.&quot; In Ebbinghaus's own table the one-day value sits 3.3 points above his formula, the largest miss of the seven, and he distrusted it. He wrote that it &quot;would fit in well with the other observations to consider the number 33.7 per cent for the relearning after 24 hours as somewhat too large,&quot; and then that &quot;it is upheld by observations to be stated presently, so that I am in doubt about it.&quot; He left it in.&lt;/p&gt;
&lt;h3 id=&quot;what-a-saving-is&quot;&gt;What a saving is&lt;/h3&gt;
&lt;p&gt;The unit matters even though the shape replicates, because a saving and a recall score are different quantities. Murre returned to the measure in 2023 with Antonio Chessa. Ebbinghaus, they write, &quot;measured memory retention in terms of the learning time saved in subsequent study trials relative to the time spent on the first learning trial,&quot; and they &quot;prove mathematically that Ebbinghaus' savings measure is independent of initial encoding strength, learning time, and relearning times.&quot; They contrast it &quot;with often used forgetting functions based on recall probability.&quot; A saving says how much faster the second learning went. It does not say what share of the material could still be produced on demand, which is what every retelling above takes it to say. And the material was lists of nonsense syllables.&lt;/p&gt;
&lt;h3 id=&quot;what-decades-look-like&quot;&gt;What decades look like&lt;/h3&gt;
&lt;p&gt;Material that means something runs on a different clock. Harry Bahrick tested 733 people on the Spanish they had learned in school, across a span of 50 years, and published the results in 1984. &quot;The analysis yields memory curves which decline exponentially for the first 3-6 years of the retention interval. After that retention remains unchanged for periods of up to 30 years before showing a final decline.&quot; Most had not kept it up: &quot;The great majority of subjects rehearse so little that the data reveal no significant rehearsal effects.&quot; Retention over the whole period was &quot;predictable on the basis of the level of original training,&quot; and &quot;Large portions of the originally acquired information remain accessible for over 50 years in spite of the fact the information is not used or rehearsed.&quot;&lt;/p&gt;
&lt;p&gt;Bahrick measured something different again, with tests of reading comprehension, recall and recognition, so his curves neither confirm nor overturn Ebbinghaus's. They show how far a table about nonsense syllables sits from a language learned in school.&lt;/p&gt;
&lt;h3 id=&quot;our-april-sentence&quot;&gt;Our April sentence&lt;/h3&gt;
&lt;p&gt;Here is the passage we published on April 26, 2026:&lt;/p&gt;
&lt;p&gt;Hermann Ebbinghaus established the lower bound in 1885. Without reinforcement, humans lose roughly half of newly learned information within an hour and most of it within a week. Modern memory research has refined the curve in countless ways, but the directional finding has held up across 140 years of experiment. &quot;We trained the team on the policy last quarter&quot; is, neurologically, close to indistinguishable from &quot;we never trained them.&quot;&lt;/p&gt;
&lt;p&gt;The direction holds: both replications above found the fast early drop and the long flattening. The rest does not. &quot;Humans&quot; was one man, and he said so. &quot;Newly learned information&quot; was the time needed to relearn lists of nonsense syllables. &quot;Most of it within a week&quot; is true only in his unit, where it was 74.6% at six days. The chapter the numbers come from reports an average curve for one person and describes no lower bound. The last sentence is contradicted by the table it leans on: a month after learning, relearning still took 21.1% less time than learning had. Nothing in the experiment was neurological either; what he measured was time. And Bahrick's former students, most of whom rehearsed too little for it to show in the data, kept large parts of their Spanish for decades.&lt;/p&gt;
&lt;p&gt;What the passage should have said: In 1879 and 1880, Hermann Ebbinghaus timed himself learning and relearning lists of nonsense syllables. An hour after learning a list, relearning it took about 56% as long as learning it had; a month later, about 79%. Replications in 1991 and 2015 found curves of the same shape.&lt;/p&gt;
&lt;h3 id=&quot;measuring-your-own&quot;&gt;Measuring your own&lt;/h3&gt;
&lt;p&gt;The part of Ebbinghaus a trainer can use is the method, and it needs a stopwatch. Time how long a group takes to reach a standard the first time: a procedure done without error, or a test passed. Some weeks later, before any refresher, time how long the same group takes to reach the same standard again. The time saved, as a share of the first session, is his measure for your material and your people. If the second session takes 70% as long as the first, the saving is 30%, and it is a number about your training.&lt;/p&gt;
&lt;p&gt;Anyone who still wants to quote the 1885 figures can, with the qualifier Ebbinghaus attached to them: &quot;for a certain individual, and for a series of 13 syllables.&quot;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;The curve fits come from &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/code/c6733/refit_ebbinghaus_c6733.py&quot;&gt;refit_ebbinghaus_c6733.py&lt;/a&gt;, which fits three shapes to the seven savings values in Ebbinghaus's summary table by least squares. Its first check is that his formula, as coded, reproduces the calculated values he printed in his formula table; it does, to within 0.1 point, but only with the exact 64 minutes from that table in place of a rounded hour, and that check is how the 64 minutes surfaced. Every quotation and figure above is checked against saved copies of its source by &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/code/c6733/figures_c6733.py&quot;&gt;figures_c6733.py&lt;/a&gt;, which also recomputes the column-IV equivalents, the MeltingSpot arithmetic and the Murre and Dros savings from their Tables 1 and 2. Both scripts and their output are in &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/code/c6733/&quot;&gt;one folder&lt;/a&gt;, with a list of every saved source the check reads, its public address, when it was saved and a checksum of the saved copy; the pages themselves are linked in the Sources below rather than republished. Ebbinghaus's tables are scanned images in the online edition; the values above were read off the scans, and his savings column agrees with Murre and Dros's transcription to the digit.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Part of &lt;strong&gt;Where the Number Came From&lt;/strong&gt;, on how a published number is a fact about the way it was measured: &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/stanford-says-12-to-66-but-12-of-what/&quot;&gt;Stanford says 12% to 66%, but 12% of what?&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/the-safety-score-is-a-fact-about-the-test-rig/&quot;&gt;The safety score is a fact about the test rig&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/95-80-42-tracing-the-2026-ai-failure-statistics/&quot;&gt;Tracing the 2026 AI-failure statistics to a primary&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sources:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Hermann Ebbinghaus, &lt;em&gt;Memory: A Contribution to Experimental Psychology&lt;/em&gt; (1885), translated by Henry A. Ruger and Clara E. Bussenius (Teachers College, Columbia University, 1913), Chapter VII: Section 28 (the design; the definition of Q; the summary table that closes the section) and Section 29 (paragraphs 1 to 3, with the formula table of observed and calculated values). Classics in the History of Psychology, York University. &lt;a rel=&quot;nofollow&quot; href=&quot;https://psychclassics.yorku.ca/Ebbinghaus/memory7.htm&quot;&gt;https://psychclassics.yorku.ca/Ebbinghaus/memory7.htm&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Jaap M. J. Murre and Joeri Dros, &quot;Replication and Analysis of Ebbinghaus' Forgetting Curve,&quot; &lt;em&gt;PLOS ONE&lt;/em&gt; 10(7): e0120644 (2015): abstract; Methods (the intervals, the subject, the differences from Ebbinghaus and from Heller et al.); Tables 1, 2 and 3; the paragraph after Table 3 (the drift correction); Discussion. &lt;a rel=&quot;nofollow&quot; href=&quot;https://doi.org/10.1371/journal.pone.0120644&quot;&gt;https://doi.org/10.1371/journal.pone.0120644&lt;/a&gt; (data: &lt;a rel=&quot;nofollow&quot; href=&quot;https://osf.io/6kfrp/&quot;&gt;https://osf.io/6kfrp/&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O. Heller, W. Mack and J. Seitz, &quot;Replikation der Ebbinghaus'schen Vergessenskurve mit der Ersparnismethode: 'Das Behalten und Vergessen als Function der Zeit',&quot; &lt;em&gt;Zeitschrift für Psychologie&lt;/em&gt; 199 (1991): 3–18. Values as reported in Murre and Dros, Table 3; the paper itself was not read.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Jaap M. J. Murre and Antonio G. Chessa, &quot;Why Ebbinghaus' savings method from 1885 is a very 'pure' measure of memory performance,&quot; &lt;em&gt;Psychonomic Bulletin &amp;amp; Review&lt;/em&gt; 30(1) (2023): 303–307, abstract. &lt;a rel=&quot;nofollow&quot; href=&quot;https://doi.org/10.3758/s13423-022-02172-3&quot;&gt;https://doi.org/10.3758/s13423-022-02172-3&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Harry P. Bahrick, &quot;Semantic memory content in permastore: Fifty years of memory for Spanish learned in school,&quot; &lt;em&gt;Journal of Experimental Psychology: General&lt;/em&gt; 113(1) (1984): 1–29, abstract. &lt;a rel=&quot;nofollow&quot; href=&quot;https://doi.org/10.1037/0096-3445.113.1.1&quot;&gt;https://doi.org/10.1037/0096-3445.113.1.1&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;David C. Rubin and Amy E. Wenzel, &quot;One hundred years of forgetting: A quantitative description of retention,&quot; &lt;em&gt;Psychological Review&lt;/em&gt; 103(4) (1996): 734–760, abstract (p. 734). &lt;a rel=&quot;nofollow&quot; href=&quot;https://doi.org/10.1037/0033-295X.103.4.734&quot;&gt;https://doi.org/10.1037/0033-295X.103.4.734&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Benoit Chatelier, &quot;Forgetting curve corporate training: the real cost,&quot; MeltingSpot blog, June 8, 2026: the sections &quot;What the Ebbinghaus forgetting curve actually tells us&quot; and &quot;The real financial cost of forgetting in L&amp;amp;D,&quot; and the paragraph before the second. &lt;a rel=&quot;nofollow&quot; href=&quot;https://meltingspot.io/en/blog/forgetting-curve-corporate-training&quot;&gt;https://meltingspot.io/en/blog/forgetting-curve-corporate-training&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Athena Marousis, &quot;What is the Ebbinghaus forgetting curve? Examples and strategies for overcoming it,&quot; TalentLMS Blog, March 30, 2023 (modified September 21, 2026): &quot;What is the forgetting curve?&quot; and the section after it. &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.talentlms.com/blog/ebbinghaus-forgetting-curve/&quot;&gt;https://www.talentlms.com/blog/ebbinghaus-forgetting-curve/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;David Boyes, &quot;Fighting the Forgetting Curve,&quot; Today's Class blog, May 5, 2023 (modified April 28, 2025): the sections &quot;What Is the 'Forgetting Curve'?&quot; and &quot;How Much Time Has Passed.&quot; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.todaysclass.com/blog/fighting-the-forgetting-curve&quot;&gt;https://www.todaysclass.com/blog/fighting-the-forgetting-curve&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TrainMeUK, &quot;Why Employees Forget 70% of Training in 24 Hours (The Ebbinghaus Effect),&quot; December 3, 2025: headline and introduction. &lt;a rel=&quot;nofollow&quot; href=&quot;https://trainmeuk.co.uk/resources/ebbinghaus-effect-employees-forget-training-24-hours&quot;&gt;https://trainmeuk.co.uk/resources/ebbinghaus-effect-employees-forget-training-24-hours&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tom McDowall, &quot;Sunday L&amp;amp;D Myth #4: People Forget 70% of What They Learn Within 24 Hours,&quot; Instructional Design Tips, November 9, 2025. &lt;a rel=&quot;nofollow&quot; href=&quot;https://idtips.substack.com/p/sunday-l-and-d-myth-4-people-forget&quot;&gt;https://idtips.substack.com/p/sunday-l-and-d-myth-4-people-forget&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&quot;We Wrote 25 Reminders and Made the Same Mistake Every Time,&quot; this site, April 26, 2026: the paragraph beginning &quot;Hermann Ebbinghaus established the lower bound.&quot; &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/we-wrote-25-reminders-and-made-the-same-mistake-every-time/&quot;&gt;https://vibeagentmaking.com/blog/we-wrote-25-reminders-and-made-the-same-mistake-every-time/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;keep-the-trail-back-to-where-the-number-was-measured-&quot;&gt;Keep the trail back to where the number was measured.&lt;/h4&gt;
&lt;p&gt;Every number in this piece survived because a script checks it against saved copies of its sources, so the trail from the figure back to the page it was measured on stays intact. A statistic that loses that trail, as &quot;half in an hour&quot; lost its unit, keeps its digits and changes its meaning. If you run agents that report numbers, keep the trail: Chain of Consciousness keeps a tamper-evident record of an agent's actions, so a figure it published can be traced back to what it actually read.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install chain-of-consciousness
npm install chain-of-consciousness
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or start without installing anything: &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/hosted-coc/&quot;&gt;Hosted Chain of Consciousness&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30006/the-forgetting-curve-everyone-cites-is-ebbinghauss-own-column-relabeled-the-90-is-not-in</guid>
<pubDate>Tue, 06 Oct 2026 13:19:27 +0000</pubDate>
</item>
<item>
<title>Building a Real-Time Social Listening &amp; Brand Sentiment Engine with Python, Gemini 1.5, and DuckDB</title>
<link>https://coderlegion.com/29747/building-real-time-social-listening-brand-sentiment-engine-with-python-gemini-and-duckdb</link>
<description>&lt;p&gt;Enterprise social listening platforms like Brandwatch or Sprout charge upwards of $800 to $2,000/month per seat. Under the hood, many still rely on brittle dictionary lookup tables (VADER-style positive/negative lexical scoring) or generic regex filters. When a developer tweets &lt;em&gt;&quot;Oh fantastic, another breaking API change deployed straight to prod on a Friday!&quot;&lt;/em&gt;, classical sentiment classifiers mark it as overwhelmingly positive due to &quot;fantastic&quot;.&lt;/p&gt;
&lt;p&gt;Meanwhile, writing your own real-time monitoring service often degrades into a distributed systems mess: rate limits across Reddit and Hacker News, unstructured LLM hallucinations, deduplication bottlenecks, and ballooning database costs.&lt;/p&gt;
&lt;p&gt;Here is how to build an asynchronous, production-ready brand intelligence engine using Python, Google's Gemini 1.5 Flash structured outputs, and DuckDB—running for less than $1/month in API costs.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-the-architecture&quot;&gt;1. The Architecture&lt;/h3&gt;
&lt;p&gt;The engine is designed around a decoupled, non-blocking ingestion and evaluation pattern:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TD
    A[Reddit API / HN Firebase / Bluesky Firehose / RSS] --&amp;gt;|Async Polling| B[Ingestion Layer: Asyncio + HTTPX]
    B --&amp;gt;|Content Hash SHA-256| C[(DuckDB Local Store - Deduplication)]
    C --&amp;gt;|New Items| D[Gemini 1.5 Flash Engine]
    D --&amp;gt;|Pydantic Structured JSON| E[Alert Dispatcher]
    E --&amp;gt;|Urgency &amp;gt;= High or Polarity &amp;lt;= -0.6| F[Slack / Discord Webhook]
    E --&amp;gt;|Daily Rollup &amp;amp; Trend Metrics| G[Parquet / Analytical Store]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;core-pillars-&quot;&gt;Core Pillars:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Async Multi-Source Ingestion:&lt;/strong&gt; Pulls from Reddit (&lt;code&gt;asyncpraw&lt;/code&gt;), Hacker News (Firebase REST API), Bluesky (AT Protocol firehose), and custom RSS feeds asynchronously.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deduplication Engine:&lt;/strong&gt; Generates an identity fingerprint (&lt;code&gt;sha256(platform + post_id)&lt;/code&gt;) checked against local embedded DuckDB before hitting LLM inference.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structured Gemini 1.5 Flash Parsing:&lt;/strong&gt; Uses schema-constrained JSON outputs to extract polarity (&lt;code&gt;-1.0&lt;/code&gt; to &lt;code&gt;1.0&lt;/code&gt;), urgency classification (&lt;code&gt;low&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt;, &lt;code&gt;critical&lt;/code&gt;), entity references, and sarcasm detection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Downstream Alert Dispatcher:&lt;/strong&gt; Routes critical events directly to Slack/Discord webhooks while appending all metadata into DuckDB for fast OLAP rollups.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-ingestion-amp-deduplication-via-duckdb&quot;&gt;2. Ingestion &amp;amp; Deduplication via DuckDB&lt;/h3&gt;
&lt;p&gt;Rather than spinning up Postgres or Redis for transient social data, we use DuckDB. DuckDB runs in-process, provides zero-latency ACID writes, and allows vector-like analytical SQL on JSON columns.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import duckdb
import hashlib

class StorageManager:
    def __init__(self, db_path: str = &quot;brand_intel.duckdb&quot;):
        self.con = duckdb.connect(db_path)
        self._init_schema()

    def _init_schema(self):
        self.con.execute(&quot;&quot;&quot;
            CREATE TABLE IF NOT EXISTS raw_mentions (
                id VARCHAR PRIMARY KEY,
                platform VARCHAR,
                author VARCHAR,
                content VARCHAR,
                url VARCHAR,
                created_at TIMESTAMP,
                processed BOOLEAN DEFAULT FALSE
            );
            CREATE TABLE IF NOT EXISTS sentiment_results (
                id VARCHAR PRIMARY KEY,
                polarity FLOAT,
                urgency VARCHAR,
                is_sarcastic BOOLEAN,
                entities JSON,
                summary VARCHAR,
                evaluated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (id) REFERENCES raw_mentions(id)
            );
        &quot;&quot;&quot;)

    def is_duplicate(self, platform: str, external_id: str) -&amp;gt; bool:
        uid = hashlib.sha256(f&quot;{platform}:{external_id}&quot;.encode()).hexdigest()
        res = self.con.execute(&quot;SELECT 1 FROM raw_mentions WHERE id = ?&quot;, [uid]).fetchone()
        return res is not None

    def insert_mention(self, platform: str, external_id: str, author: str, content: str, url: str, created_at):
        uid = hashlib.sha256(f&quot;{platform}:{external_id}&quot;.encode()).hexdigest()
        self.con.execute(&quot;&quot;&quot;
            INSERT INTO raw_mentions (id, platform, author, content, url, created_at)
            VALUES (?, ?, ?, ?, ?, ?)
            ON CONFLICT (id) DO NOTHING
        &quot;&quot;&quot;, [uid, platform, author, content, url, created_at])
        return uid
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-structured-gemini-1-5-flash-evaluation&quot;&gt;3. Structured Gemini 1.5 Flash Evaluation&lt;/h3&gt;
&lt;p&gt;To eliminate hallucinations and avoid parsing regex out of freeform LLM completions, we leverage Google GenAI's native Pydantic schema validation. We use &lt;code&gt;gemini-1.5-flash&lt;/code&gt; due to its sub-second latency and $0.075 per 1M tokens cost tier.&lt;/p&gt;
&lt;h4 id=&quot;the-evaluation-schema&quot;&gt;The Evaluation Schema&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pydantic import BaseModel, Field
from typing import List, Literal
import google.generativeai as genai
import os

genai.configure(api_key=os.environ[&quot;GEMINI_API_KEY&quot;])

class SentimentEvaluation(BaseModel):
    polarity: float = Field(
        description=&quot;Polarity score strictly between -1.0 (extremely negative/hostile) and 1.0 (enthusiastic/positive).&quot;
    )
    urgency: Literal[&quot;low&quot;, &quot;medium&quot;, &quot;high&quot;, &quot;critical&quot;] = Field(
        description=&quot;'critical' indicates data breaches, service downtime, or legal threats; 'high' indicates severe bugs or churn threats; 'medium' is regular feedback; 'low' is passive mention.&quot;
    )
    is_sarcastic: bool = Field(description=&quot;True if the statement implies the opposite of its literal words.&quot;)
    entities: List[str] = Field(description=&quot;List of specific products, competitor names, or people mentioned.&quot;)
    summary: str = Field(description=&quot;1-sentence technical digest of the user's issue or praise.&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;inference-function-with-enforced-json-schema&quot;&gt;Inference Function with Enforced JSON Schema&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import json

def evaluate_mention(brand_name: str, text: str) -&amp;gt; SentimentEvaluation:
    prompt = f&quot;&quot;&quot;
    Analyze the following social mention referencing the brand: '{brand_name}'.
    Contextualize sarcasm, tech nuances, and developer sentiment.
    
    Content:
    &quot;&quot;&quot;{text}&quot;&quot;&quot;
    &quot;&quot;&quot;
    
    model = genai.GenerativeModel(&quot;gemini-1.5-flash&quot;)
    
    response = model.generate_content(
        prompt,
        generation_config=genai.GenerationConfig(
            response_mime_type=&quot;application/json&quot;,
            response_schema=SentimentEvaluation,
            temperature=0.1,
        ),
    )
    
    return SentimentEvaluation.model_validate_json(response.text)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-threshold-alerting-amp-webhook-routing&quot;&gt;4. Threshold Alerting &amp;amp; Webhook Routing&lt;/h3&gt;
&lt;p&gt;When a negative spike occurs (e.g., a critical authentication vulnerability drops on Hacker News), latency matters. The dispatcher inspects the structured output and fires immediate payloads to Slack, Discord, or an incident management endpoint like PagerDuty.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import httpx
import asyncio

SLACK_WEBHOOK_URL = os.environ.get(&quot;SLACK_WEBHOOK_URL&quot;)

async def dispatch_alert(eval_data: SentimentEvaluation, platform: str, url: str, content: str):
    # Alert trigger: high/critical urgency OR heavily negative sentiment
    if eval_data.urgency in [&quot;high&quot;, &quot;critical&quot;] or eval_data.polarity &amp;lt;= -0.5:
        color = &quot;#FF0000&quot; if eval_data.urgency == &quot;critical&quot; else &quot;#FFA500&quot;
        
        payload = {
            &quot;attachments&quot;: [
                {
                    &quot;color&quot;: color,
                    &quot;title&quot;: f&quot;🚨 Brand Alert [{eval_data.urgency.upper()}] on {platform}&quot;,
                    &quot;title_link&quot;: url,
                    &quot;fields&quot;: [
                        {&quot;title&quot;: &quot;Summary&quot;, &quot;value&quot;: eval_data.summary, &quot;short&quot;: False},
                        {&quot;title&quot;: &quot;Polarity&quot;, &quot;value&quot;: f&quot;{eval_data.polarity:.2f}&quot;, &quot;short&quot;: True},
                        {&quot;title&quot;: &quot;Sarcasm Detected&quot;, &quot;value&quot;: str(eval_data.is_sarcastic), &quot;short&quot;: True},
                        {&quot;title&quot;: &quot;Entities&quot;, &quot;value&quot;: &quot;, &quot;.join(eval_data.entities) or &quot;None&quot;, &quot;short&quot;: True},
                    ],
                    &quot;text&quot;: f&quot;*Snippet:*
&amp;gt; {content[:280]}...&quot;,
                }
            ]
        }
        
        async with httpx.AsyncClient() as client:
            resp = await client.post(SLACK_WEBHOOK_URL, json=payload)
            resp.raise_for_status()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-deployment-concurrency-amp-rate-limits&quot;&gt;5. Deployment, Concurrency &amp;amp; Rate Limits&lt;/h3&gt;
&lt;p&gt;Running high-frequency pollers against varied APIs presents two main issues:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Rate Limit Exhaustion:&lt;/strong&gt; Reddit restricts standard OAuth to 60 requests/minute. Gemini 1.5 Flash has a free-tier limit of 15 RPM and Tier-1 pay-as-you-go limit of 1,000 RPM.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event Loop Starvation:&lt;/strong&gt; Blocking HTTP calls freeze background listeners.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;the-concurrency-pool-pattern&quot;&gt;The Concurrency Pool Pattern&lt;/h4&gt;
&lt;p&gt;Use an &lt;code&gt;asyncio.Queue&lt;/code&gt; paired with worker pools to guarantee your workers respect rate bounds:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;async def analysis_worker(queue: asyncio.Queue, db: StorageManager, brand_name: str):
    while True:
        item = await queue.get()
        uid, platform, author, content, url = item
        try:
            # Non-blocking run for Gemini evaluation
            eval_res = await asyncio.to_thread(evaluate_mention, brand_name, content)
            
            # Save analytics to DuckDB
            db.con.execute(&quot;&quot;&quot;
                INSERT INTO sentiment_results (id, polarity, urgency, is_sarcastic, entities, summary)
                VALUES (?, ?, ?, ?, ?, ?)
            &quot;&quot;&quot;, [uid, eval_res.polarity, eval_res.urgency, eval_res.is_sarcastic, 
                  json.dumps(eval_res.entities), eval_res.summary])
            
            # Route Alerts
            await dispatch_alert(eval_res, platform, url, content)
            
        except Exception as e:
            print(f&quot;[ERROR] Failed to process {uid}: {e}&quot;)
        finally:
            queue.task_done()
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;analytical-daily-rollups-via-duckdb&quot;&gt;Analytical Daily Rollups via DuckDB&lt;/h4&gt;
&lt;p&gt;To view aggregated brand health over the past 24 hours without loading heavy Pandas workflows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT 
    date_trunc('hour', evaluated_at) as hour_bucket,
    AVG(polarity) as avg_sentiment,
    COUNT(CASE WHEN urgency IN ('high', 'critical') THEN 1 END) as critical_count,
    COUNT(*) as total_mentions
FROM sentiment_results
WHERE evaluated_at &amp;gt;= NOW() - INTERVAL '24 HOURS'
GROUP BY 1
ORDER BY 1 DESC;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-conclusion-amp-turnkey-template&quot;&gt;6. Conclusion &amp;amp; Turnkey Template&lt;/h3&gt;
&lt;p&gt;With under 200 lines of modern Python, you can replace a legacy multi-thousand-dollar monitoring subscription with a real-time system that catches sarcasm, tags entities, writes to low-footprint local storage, and pings your on-call channel in real time.&lt;/p&gt;
&lt;p&gt;You can manually implement this architecture using the snippets above, wire your own OAuth credentials, and maintain the worker pipelines yourself.&lt;/p&gt;
&lt;p&gt;If you prefer to deploy a battle-tested, production-ready codebase immediately—complete with pre-configured connectors for Reddit, Bluesky, Hacker News, RSS feeds, an automated CLI dashboard, and native Docker Compose files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://whop.com/mr-studio-7faa/real-time-brand-sentiment-social-listening-engine-python-gemini&quot;&gt;Instant Access on Whop&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://mannyverse767.gumroad.com/l/cpmgnd&quot;&gt;Direct Download on Gumroad&lt;/a&gt;&lt;/strong&gt; (Use promo code &lt;strong&gt;&lt;code&gt;EARLYBIRD&lt;/code&gt;&lt;/strong&gt; for 20% off)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have questions about tuning the schema for specific B2B niche contexts? Drop a comment below!&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/29747/building-real-time-social-listening-brand-sentiment-engine-with-python-gemini-and-duckdb</guid>
<pubDate>Sun, 04 Oct 2026 16:57:35 +0000</pubDate>
</item>
<item>
<title>Reading a rental-yield CSV without inventing missing data</title>
<link>https://coderlegion.com/29358/reading-a-rental-yield-csv-without-inventing-missing-data</link>
<description>&lt;p&gt;A small CSV can be easy to load and surprisingly easy to misinterpret. This walkthrough uses dataHabibi's owned June 2026 Dubai community rental-yield snapshot to show how to preserve units, missing values and the distinction between a community and a city aggregate.&lt;/p&gt;
&lt;p&gt;Disclosure: I am Haron Merzaie, co-founder of dataHabibi alongside Ibrahim Qorraj. The source platform is &lt;a rel=&quot;nofollow&quot; href=&quot;https://datahabibi.ae/&quot;&gt;https://datahabibi.ae/&lt;/a&gt;. This is a historical data-reading example, not a live feed or an investment recommendation.&lt;/p&gt;
&lt;h3 id=&quot;start-with-the-file-s-actual-schema&quot;&gt;Start with the file's actual schema&lt;/h3&gt;
&lt;p&gt;The CSV has six columns: &lt;code&gt;Community&lt;/code&gt;, &lt;code&gt;Gross rental yield percent&lt;/code&gt;, &lt;code&gt;Buildings tracked&lt;/code&gt;, &lt;code&gt;Yield sample size&lt;/code&gt;, &lt;code&gt;Snapshot&lt;/code&gt;, and &lt;code&gt;Source&lt;/code&gt;. There are twelve named community rows and one row labelled &lt;code&gt;Dubai city average&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The dataset and its field documentation are available in the public repository &lt;code&gt;datahabibi-bot/dubai-community-yields-june-2026&lt;/code&gt; on GitHub. Download &lt;code&gt;dubai-community-yields-june-2026.csv&lt;/code&gt; from that repository and put it beside your Python script.&lt;/p&gt;
&lt;p&gt;For example, the International City row records 9.98 in the yield field, 358 buildings tracked and a yield sample size of 220. The city-average row records 8.16 and 2,561 buildings, but has a blank sample-size field. These are values in this particular snapshot; they do not describe today's market.&lt;/p&gt;
&lt;h3 id=&quot;preserve-percentages-and-missing-values&quot;&gt;Preserve percentages and missing values&lt;/h3&gt;
&lt;p&gt;The value 9.98 means 9.98 percent. If a later calculation needs a decimal fraction, convert it explicitly by dividing by 100. Do not silently switch between percentage points and fractions.&lt;/p&gt;
&lt;p&gt;The blank city-average sample size means the source did not supply it. Converting it to zero would change the meaning of the file. Represent it as &lt;code&gt;None&lt;/code&gt; instead.&lt;/p&gt;
&lt;p&gt;This standard-library example preserves those distinctions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import csv
from decimal import Decimal

with open(&quot;dubai-community-yields-june-2026.csv&quot;, newline=&quot;&quot;, encoding=&quot;utf-8&quot;) as source:
    rows = list(csv.DictReader(source))

records = []
for row in rows:
    sample_text = row[&quot;Yield sample size&quot;].strip()
    records.append({
        &quot;community&quot;: row[&quot;Community&quot;],
        &quot;yield_percent&quot;: Decimal(row[&quot;Gross rental yield percent&quot;]),
        &quot;buildings_tracked&quot;: int(row[&quot;Buildings tracked&quot;]),
        &quot;yield_sample_size&quot;: int(sample_text) if sample_text else None,
        &quot;snapshot&quot;: row[&quot;Snapshot&quot;],
    })

communities = [r for r in records if r[&quot;community&quot;] != &quot;Dubai city average&quot;]
city_rows = [r for r in records if r[&quot;community&quot;] == &quot;Dubai city average&quot;]

for record in communities:
    print(record[&quot;community&quot;], record[&quot;yield_percent&quot;], record[&quot;yield_sample_size&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Decimal&lt;/code&gt; keeps the recorded decimal representation explicit. The original CSV remains unchanged, so another reader can distinguish source values from transformations in your code.&lt;/p&gt;
&lt;h3 id=&quot;do-not-manufacture-an-aggregation-method&quot;&gt;Do not manufacture an aggregation method&lt;/h3&gt;
&lt;p&gt;Separating &lt;code&gt;city_rows&lt;/code&gt; prevents the city aggregate from being treated as a thirteenth community. It also avoids accidentally including the aggregate in an average of community rows.&lt;/p&gt;
&lt;p&gt;The snapshot does not specify the sample unit, weights or aggregation method. &lt;code&gt;Buildings tracked&lt;/code&gt; therefore cannot be assumed to be the yield denominator. The displayed community rows cannot independently reconstruct the city-average yield. A chart can show recorded values, but its caption should name the June 2026 period and explain that limitation.&lt;/p&gt;
&lt;p&gt;The CSV also does not include the underlying rents and property prices needed to recompute yields. Gross rental yield is different from net yield or total return. Service charges, vacancy, maintenance, financing and transaction costs are not fields in this file. It contains no price forecast or guaranteed future return.&lt;/p&gt;
&lt;h3 id=&quot;make-reuse-traceable&quot;&gt;Make reuse traceable&lt;/h3&gt;
&lt;p&gt;The owned dataset is released under CC BY 4.0. When sharing an adaptation, credit dataHabibi, identify the June 2026 snapshot, include the licence and indicate changes. The dataset release does not include rights to the logo or commercial platform source code.&lt;/p&gt;
&lt;p&gt;The practical lesson is small: retain the source file, describe its units, preserve blanks, separate aggregate rows and state what the data cannot establish. Those choices make a simple CSV analysis easier for another developer to inspect and reproduce.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29358/reading-a-rental-yield-csv-without-inventing-missing-data</guid>
<pubDate>Thu, 01 Oct 2026 06:55:49 +0000</pubDate>
</item>
<item>
<title>FMCSA Served 16,414 Suspension Notices in Four Months. Five of the 280 I Checked Owned No Trucks.</title>
<link>https://coderlegion.com/29356/fmcsa-served-16-414-suspension-notices-in-four-months-five-of-the-280-checked-owned-trucks</link>
<description>&lt;blockquote&gt;&lt;p&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/fmcsa-16414-suspensions-five-of-280-owned-no-trucks/&quot;&gt;vibeagentmaking.com&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On January 16, 2026, the last piece of a federal rule about freight brokers came into force. Under 49 CFR 387.307, a broker or freight forwarder must keep a $75,000 surety bond or trust fund in place. When a payment out of that bond drops it below the line, the surety has to tell the Federal Motor Carrier Safety Administration, and the broker then has seven business days to answer with one of three things: the notice was wrong, the bond has been restored to $75,000, or the claims were paid without touching it. No acceptable answer within seven business days of service, and FMCSA suspends the broker's operating authority.&lt;/p&gt;
&lt;p&gt;That is a real power, and the trade press read it as one: a clock that can take a broker out of business in a week and a half. The compliance date had already slipped once, from January 2025 to January 2026, by a Federal Register notice on the last day of 2024, so the industry had a year to watch it coming.&lt;/p&gt;
&lt;p&gt;The obvious follow-up question is how many brokers the government has actually suspended since. The government publishes the file that answers it. What the file says is not what the coverage implied.&lt;/p&gt;
&lt;h3 id=&quot;the-count&quot;&gt;The count&lt;/h3&gt;
&lt;p&gt;FMCSA's suspension record on data.transportation.gov holds 18,269 orders. Of those, 16,414 are of one type, &quot;Operating Authority Involuntary Suspension Notice.&quot; The other 1,855 are voluntary suspensions, where the operator asked for it. The serve dates run from May 18 to September 12, 2026, a span of 118 days.&lt;/p&gt;
&lt;p&gt;Sixteen thousand involuntary suspensions in 118 days is 139 a day. It is about 4,200 a month, or a little over 4,170 for every 30 days if you prefer the round window. Ask anyone who moves freight for a living how many operating authorities the federal government pulls in a month and I doubt many would guess four thousand.&lt;/p&gt;
&lt;p&gt;So the January rule sits inside a suspension programme running at industrial scale. The question is whether the rule is what's driving it.&lt;/p&gt;
&lt;h3 id=&quot;who-is-being-suspended&quot;&gt;Who is being suspended&lt;/h3&gt;
&lt;p&gt;The suspension file has a limitation the coverage never mentions: it identifies each entity by USDOT number and carries no docket number. A USDOT number tells you nothing about what kind of business holds it. A trucking company, a broker and a freight forwarder all have one. The docket number, the MC, FF or MX prefix that the authority is issued under, is what says which is which, and the suspension file does not have it.&lt;/p&gt;
&lt;p&gt;The registry does. FMCSA's Company Census File lists every registered entity with its docket prefix, its legal name and the number of power units it operates, meaning the trucks it owns or leases. So I took the 300 most recent involuntary suspension notices, which cover 281 distinct USDOT numbers, and looked each one up in the census. Two hundred and eighty matched, a join rate of 99.6 percent.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;docket prefix of the suspended entity &lt;/th&gt;&lt;th&gt;count &lt;/th&gt;&lt;th&gt;share of the 280&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;MC (motor carrier or property broker) &lt;/td&gt;&lt;td&gt;264 &lt;/td&gt;&lt;td&gt;94.3%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;no docket in the census record &lt;/td&gt;&lt;td&gt;14 &lt;/td&gt;&lt;td&gt;5.0%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;FF (freight forwarder) &lt;/td&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;0.4%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;MX (Mexico-domiciled carrier) &lt;/td&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;0.4%&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;One freight forwarder in 280. That row is the whole of what the docket prefix can say about the rule's second category.&lt;/p&gt;
&lt;p&gt;For brokers the prefix cannot say anything on its own, because MC covers both property brokers and motor carriers. The census has a second column that can. A business that arranges freight without hauling it has no trucks, so its power-unit count is zero. Of the 280 matched entities, five have zero power units. That is 1.8 percent.&lt;/p&gt;
&lt;p&gt;Those five are named in the public record, and a reader who wants to check the join can open the rows one at a time: Get A-Long Logistics LLC, USA Airfish Logistic LLC, Champion Transportation and Logistics LLC, Solidify Logistics LLC, Dorpojo Express LLC. Naming them says nothing about why any of them was suspended. The file does not record a reason, and neither do I. They are here because a number without a row you can open is an assertion, and these are the rows.&lt;/p&gt;
&lt;p&gt;The other 275 entities own trucks. Whatever suspended them, it was not a broker bond.&lt;/p&gt;
&lt;h3 id=&quot;what-that-makes-the-january-rule&quot;&gt;What that makes the January rule&lt;/h3&gt;
&lt;p&gt;On this evidence the broker-suspension power is not driving the volume. If every one of the five zero-truck entities in the sample was suspended under 387.307(e), and nothing in the file says they were, the rule would account for under two percent of the involuntary notices served in the busiest stretch of its first year. The remaining 98 percent are pointed at trucking companies.&lt;/p&gt;
&lt;p&gt;What suspends a trucking company's operating authority thousands of times a month is, in the industry's standard account, insurance. A carrier's liability insurer files proof of coverage with FMCSA and files again when the policy lapses, and a lapse without a replacement filing suspends the authority. I want to be careful about the status of that sentence. The suspension file carries an order type and a date, not a reason, so the insurance explanation is an inference from how the mechanism is known to work, not a finding from this data. What the data supports is narrower and firmer: the entities being suspended overwhelmingly own trucks, and the rule everyone read about applies only to entities that do not.&lt;/p&gt;
&lt;h3 id=&quot;what-the-record-cannot-tell-you&quot;&gt;What the record cannot tell you&lt;/h3&gt;
&lt;p&gt;Three gaps, each of them measured rather than assumed.&lt;/p&gt;
&lt;p&gt;The suspension history begins on May 18, 2026. The rule commenced on January 16. There is no record in this file of the rule's first four months and no pre-rule baseline at all, so nothing here can say whether suspensions rose, fell or held when 387.307(e) arrived. Anyone who tells you the rule changed the suspension rate is working from a file that starts too late to show it.&lt;/p&gt;
&lt;p&gt;The revocation file, a separate record of a separate order, has deep history: 1,529,083 rows. It can be cut by docket prefix, and 5,944 of its rows are FF dockets. Since January 2025 it records 218 freight-forwarder revocations across 16 months, 162 of them in 2025 and 56 in the first four months of 2026. Then the series stops. Its last month is April 2026, five months before the day I read it. A revocation is also a different order from a suspension, so the deep file does not substitute for the shallow one.&lt;/p&gt;
&lt;p&gt;And the docket prefix only half-separates the population. FF isolates forwarders cleanly. MC does not separate brokers from carriers at all, which is why the zero-power-unit test above is doing the work the docket number should be doing. That test is a proxy. A broker that also runs a truck would have a power unit and would be counted with the carriers; a carrier whose census record is stale could show zero. Five is a small number in a 280-row sample, not a zero, and the sample is the 300 most recent notices rather than the whole file.&lt;/p&gt;
&lt;h3 id=&quot;the-operator-s-takeaway&quot;&gt;The operator's takeaway&lt;/h3&gt;
&lt;p&gt;Every trade article about 387.307 was, in effect, a warning to brokers about a week-and-a-half clock. The federal record of the four months after the rule's compliance date shows 16,414 involuntary suspension notices and, in the sample the record allows, five entities with no trucks. The lever exists. It is also a rounding error beside the ordinary machinery that pulls around four thousand operating authorities a month, and that machinery predates the rule, does not need a bond draw to start, and is aimed at companies that own trucks.&lt;/p&gt;
&lt;p&gt;If you run a fleet, the thing most likely to suspend your authority is the boring thing, and it was already there before anyone wrote about the new one.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;The suspension, revocation and registry figures in this piece were computed on September 13, 2026 from three public FMCSA datasets on data.transportation.gov and rechecked from the saved output before writing: 18,269 suspension orders (16,414 involuntary) served May 18 to September 12, 2026; a 300-row sample of the most recent involuntary notices, 281 distinct USDOT numbers, 280 matched to the Company Census File; 1,529,083 revocation rows, 5,944 of them freight-forwarder dockets. Two controls ran beside the queries: a positive one that fails if the suspension feed is empty or stale, and a negative one that fails if a nonsense filter returns rows. Both passed. The three endpoints in Sources below are the whole of what these figures were computed from, so anyone can re-run them and compare: the order type is &quot;Operating Authority Involuntary Suspension Notice&quot;, the window is the serve-date span given above, and the sample is the 300 most recent notices of that type.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Part of &lt;strong&gt;Where the Number Came From&lt;/strong&gt;, on how a published number is a fact about the way it was measured: &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/stanford-says-12-to-66-but-12-of-what/&quot;&gt;Stanford says 12% to 66%, but 12% of what?&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/the-safety-score-is-a-fact-about-the-test-rig/&quot;&gt;The safety score is a fact about the test rig&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/95-80-42-tracing-the-2026-ai-failure-statistics/&quot;&gt;Tracing the 2026 AI-failure statistics to a primary&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sources:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;FMCSA, &lt;em&gt;Motus RevokeSuspend, All With History&lt;/em&gt; (suspension orders by USDOT number, order type and serve date). &lt;a rel=&quot;nofollow&quot; href=&quot;https://data.transportation.gov/resource/wb4f-neki.json&quot;&gt;https://data.transportation.gov/resource/wb4f-neki.json&lt;/a&gt;, read September 13, 2026.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FMCSA, &lt;em&gt;Revocation, All With History&lt;/em&gt; (revocation orders by docket number). &lt;a rel=&quot;nofollow&quot; href=&quot;https://data.transportation.gov/resource/sa6p-acbp.json&quot;&gt;https://data.transportation.gov/resource/sa6p-acbp.json&lt;/a&gt;, read September 13, 2026.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FMCSA, &lt;em&gt;Company Census File&lt;/em&gt; (docket prefix, legal name, power units, status). &lt;a rel=&quot;nofollow&quot; href=&quot;https://data.transportation.gov/resource/az4n-8mr2.json&quot;&gt;https://data.transportation.gov/resource/az4n-8mr2.json&lt;/a&gt;, read September 13, 2026.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;49 CFR § 387.307, &lt;em&gt;Property broker surety bond or trust fund&lt;/em&gt;, paragraphs (a) and (e); the section states it is effective January 16, 2026. &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.law.cornell.edu/cfr/text/49/387.307&quot;&gt;https://www.law.cornell.edu/cfr/text/49/387.307&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FMCSA, &lt;em&gt;Broker and Freight Forwarder Financial Responsibility&lt;/em&gt;, final rule, Federal Register Vol. 88 No. 220, November 16, 2023. &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.govinfo.gov/content/pkg/FR-2023-11-16/html/2023-25312.htm&quot;&gt;https://www.govinfo.gov/content/pkg/FR-2023-11-16/html/2023-25312.htm&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FMCSA, &lt;em&gt;Broker and Freight Forwarder Financial Responsibility; Extension of Compliance Date&lt;/em&gt;, 89 FR 107021, December 31, 2024 (&quot;Brokers, freight forwarders, surety providers, and financial institutions must comply with all the provisions of Sec. 387.307 beginning on January 16, 2026.&quot;). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.govinfo.gov/content/pkg/FR-2024-12-31/html/2024-30509.htm&quot;&gt;https://www.govinfo.gov/content/pkg/FR-2024-12-31/html/2024-30509.htm&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Data &amp; AI</category>
<guid isPermaLink="true">https://coderlegion.com/29356/fmcsa-served-16-414-suspension-notices-in-four-months-five-of-the-280-checked-owned-trucks</guid>
<pubDate>Thu, 01 Oct 2026 06:11:36 +0000</pubDate>
</item>
<item>
<title>Forensic Aviation Analytics: Auditing Fuel Bleeds &amp; Delay Cascades on a Regional A320neo Network</title>
<link>https://coderlegion.com/28910/forensic-aviation-analytics-auditing-fuel-bleeds-delay-cascades-regional-a320neo-network</link>
<description>&lt;p&gt;When evaluating airline performance, typical analytics dashboards rely blindly on passenger-facing &quot;On-Time&quot; arrival percentages. However, from an operational and financial engineering standpoint, these high-level metrics can easily mask millions of dollars in silent cash destruction. &lt;/p&gt;
&lt;p&gt;Due to strategic schedule padding, a short-haul flight can sit idling its engines on a congested tarmac for 20 minutes, yet still be registered as &quot;On-Time&quot; to the customer. The passenger is happy, but the airline’s finance department just watched pure margin vaporize into thin air.&lt;/p&gt;
&lt;p&gt;To expose these hidden friction layers, I engineered LCC-Ops: an end-to-end data warehouse auditing over 120,000 regional transaction legs modeled around the single-aisle network performance of flynas across key GCC hubs.&lt;/p&gt;
&lt;p&gt;Here is the technical architecture and the forensic database methodologies behind the pipeline.&lt;/p&gt;
&lt;h3 id=&quot;the-tech-stack-amp-architecture&quot;&gt;The Tech Stack &amp;amp; Architecture&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Database Layer:&lt;/strong&gt; MySQL Server 8.0 (Advanced CTEs, Mathematical Window Functions, Schema Migrations)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Business Intelligence:&lt;/strong&gt; Power BI Desktop (Data Type Optimization, Cross-Table Iterators, UI Design)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Core Fleet Constants:&lt;/strong&gt; Engineered around the Airbus A320neo (CFM LEAP-1A26 engine) utilizing official ICAO Engine Emissions Databank metrics. At a standard 7% ground idle taxi thrust, this engine burns exactly 0.0940 kg of fuel per second, per engine&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-database-engineering-breakdown&quot;&gt;The Database Engineering Breakdown&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Pillar 1&lt;/strong&gt;: Isolating Tarmac Cash Destruction (Domain A)To find the true cost of airport ground bottlenecks, the ingestion engine isolates rows inside the transactions table (fact_flights) where the Out-Gate to Runway Takeoff latency (taxi_out) exceeded a standard 4-minute engine stabilization and warming buffer.&lt;/p&gt;
&lt;p&gt;By pulling the real-world ICAO fuel coefficients down via relational keys, the calculation bypasses text-based padding and computes raw physical fuel destruction.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT 
    f.origin AS departure_hub,
    COUNT(f.tail_number) AS total_monitored_flights,
    ROUND(AVG(f.taxi_out), 2) AS avg_taxi_out_minutes,
    -- Formula: (Actual Taxi Time - 4 Min Buffer) * 60 Sec * ICAO Idle Flow Rate * 2 Engines
    ROUND(SUM((f.taxi_out - 4) * 60 * e.fuel_flow_idle_kg_sec * 2), 2) AS total_wasted_fuel_kg
FROM fact_flights f
JOIN dim_aircraft a ON f.tail_number = a.tail_number
JOIN dim_engine e ON a.engine_identification = e.engine_identification
WHERE f.reporting_airline = 'XY' AND f.taxi_out &amp;gt; 4
GROUP f.origin;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Pillar 2: Tracking Cross-Border Delay Cascades (Domain D)&lt;/strong&gt;&lt;br&gt;
When an international flight arriving from an airport like Karachi (KHI) experiences customs or airspace delays, that latency risks cascading into the aircraft’s subsequent domestic legs (e.g., RUH ➔ JED).&lt;/p&gt;
&lt;p&gt;To track this chronologically across individual daily airframe lifecycles, I utilized advanced LEAD() mathematical window functions&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WITH ranked_daily_schedule AS (
    SELECT 
        flight_date, tail_number, origin, dest, carrier_delay AS leg_1_delay,
        LEAD(origin, 1) OVER (PARTITION BY tail_number, flight_date ORDER BY taxi_out DESC) AS leg_2_origin,
        LEAD(carrier_delay, 1) OVER (PARTITION BY tail_number, flight_date ORDER BY taxi_out DESC) AS leg_2_delay
    FROM fact_flights
    WHERE reporting_airline = 'XY'
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Pillar 3: Menu Logistics &amp;amp; Hangar Risk Automation (Domains B &amp;amp; C)&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Catering Spoilage Optimization:&lt;/em&gt; Short flight windows on regional tracks mean cabin crews lack the time to complete complex perishable sales loops. The data models validate flynas’s lean, shelf-stable buy-on-board menu model, proving that switching to fresh profiles would trigger an immediate &lt;strong&gt;54% inventory spoilage cash bleed&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; &lt;em&gt;Automated Hangar Reorder Points (ROP):&lt;/em&gt; The warehouse tracks rolling airframe hours against specific tail numbers (HZ-NS1 to HZ-NS5) [Query-relevant Context]. If a component approaches its high-wear safety limit, the system triggers automated database alerts to protect the airline against catastrophic &lt;strong&gt;$25,000/day Aircraft on Ground (AOG) hangar penalties&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;ui-engineering-in-power-bi&quot;&gt;UI Engineering in Power BI&lt;/h3&gt;
&lt;p&gt;To present these data layers to corporate stakeholders, I built an interactive application-style interface directly inside Power BI using flynas’s signature branding tones.&lt;/p&gt;
&lt;p&gt;The biggest challenge during BI ingestion was data type conversions—empty strings or trailing spaces in the raw logs caused math functions to drop blank values (--). By implementing upfront string transformations (Uppercase/Trim) and embedding data validation wrappers like ISNUMBER(VALUE()) directly into row-level DAX iterators, the front-end dynamically scales across massive transaction volumes without data loss.&lt;/p&gt;
&lt;p&gt;The entire production code schema and scripts are live on my GitHub &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/hafsamotiwala/flynas-fleet-telemetry-analytics&quot;&gt;Link&lt;/a&gt;. I'd love to hear how other data engineers manage real-time fuel-burn parameters or multi-leg network routing in your pipelines!&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/28910/forensic-aviation-analytics-auditing-fuel-bleeds-delay-cascades-regional-a320neo-network</guid>
<pubDate>Mon, 28 Sep 2026 11:41:49 +0000</pubDate>
</item>
<item>
<title>Monte Carlo Permutation Testing in Python: How Random Trade Order Falsifies Your Equity Curve</title>
<link>https://coderlegion.com/28876/monte-carlo-permutation-testing-python-how-random-trade-order-falsifies-your-equity-curve</link>
<description>&lt;p&gt;Your backtest equity curve is merely &lt;strong&gt;one historical trajectory out of millions of equally probable alternatives&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The total profit of your trading system is invariant to order: 100 closed trades will yield the exact same ending dollar return whether the winning trades arrive first or last. However, &lt;strong&gt;path-dependent survivability metrics&lt;/strong&gt;—Maximum Drawdown, Ulcer Index, Margin Call Probability, and Time-to-Recovery—are dominated by trade sequence.&lt;/p&gt;
&lt;p&gt;If a cluster of 6 normal consecutive losses strikes at trade #1 instead of trade #70, an account operating under a strict 10% risk floor (common in institutional mandates and proprietary evaluation rules) is liquidated before the positive edge ever materializes.&lt;/p&gt;
&lt;p&gt;In this article, we formulate and implement a &lt;strong&gt;vectorized Monte Carlo permutation engine in Python&lt;/strong&gt; to rigorously falsify equity curves before committing live capital.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-permutation-vs-bootstrap-the-critical-distinction&quot;&gt;1. Permutation vs. Bootstrap: The Critical Distinction&lt;/h3&gt;
&lt;p&gt;Many trading platforms market &quot;Monte Carlo analysis&quot; without disclosing their resampling methodology. In quantitative finance, the distinction is fundamental:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Dimension &lt;/th&gt;&lt;th&gt;Permutation Testing (Without Replacement) &lt;/th&gt;&lt;th&gt;Bootstrap Resampling (With Replacement)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Mechanism&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Random shuffle of historical trade order &lt;/td&gt;&lt;td&gt;Random draw where any trade can be selected $k$ times&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;P&amp;amp;L Distribution&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;100% Identical&lt;/strong&gt; to sample (same mean, win rate, skewness) &lt;/td&gt;&lt;td&gt;Creates synthetic distributions with altered win rates&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Statistical Target&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Isolates &lt;strong&gt;sequence risk&lt;/strong&gt; alone &lt;/td&gt;&lt;td&gt;Estimates sampling error under $i.i.d.$ assumption&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Vulnerability&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Assumes trades are conditionally independent &lt;/td&gt;&lt;td&gt;Heavily distorts tail risk if outliers are oversampled&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For validating an existing trading journal or strategy backtest, &lt;strong&gt;Permutation Testing is the strict falsification baseline&lt;/strong&gt;. It asks the minimal, humble question: &lt;em&gt;&quot;Given the exact trades we actually took, what percentage of alternate timelines would have breached our risk budget?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-the-4-path-metrics-that-expose-hidden-ruin&quot;&gt;2. The 4 Path Metrics That Expose Hidden Ruin&lt;/h3&gt;
&lt;p&gt;Looking solely at backtest Sharpe ratio or nominal net profit hides ruin. When running $N=5,000$ permutations, evaluate these four metrics:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Probability of Ruin ($P_{\text{ruin}}$):&lt;/strong&gt; The percentage of shuffled paths whose equity breaches the hard drawdown barrier (e.g., $9,000$ on a $\$10,000$ starting balance) at any point along the horizon.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Median Max Drawdown ($MDD_{50}$):&lt;/strong&gt; The central tendency of drawdown. If your backtest showed a 7% drawdown but the median shuffled drawdown is 16%, your historical curve was an unusually lucky sequence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;95th Percentile Max Drawdown ($MDD_{95}$):&lt;/strong&gt; The stress-test boundary. 95% of simulated timelines experienced a drawdown less severe than this number; 5% suffered worse. This is your true operational capital requirement.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drawdown Dispersion Ratio:&lt;/strong&gt; $MDD&lt;em&gt;{95} / MDD&lt;/em&gt;{50}$. A high ratio indicates severe tail-sequence vulnerability.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-vectorized-python-implementation-numpy-&quot;&gt;3. Vectorized Python Implementation (NumPy)&lt;/h3&gt;
&lt;p&gt;Iterating across 5,000 simulations using Python loops is computationally inefficient. Below is a high-performance, fully vectorized implementation using &lt;code&gt;numpy.random.default_rng().permuted&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np
from typing import Dict, Any

def run_monte_carlo_permutation(
    trades: np.ndarray,
    initial_capital: float = 10000.0,
    ruin_capital: float = 9000.0,
    num_simulations: int = 5000,
    seed: int = 42
) -&amp;gt; Dict[str, Any]:
    &quot;&quot;&quot;
    Vectorized Monte Carlo Permutation Test (Resampling Without Replacement).
    Evaluates sequence risk and ruin probability on trade P&amp;amp;L arrays.
    &quot;&quot;&quot;
    trades = np.asarray(trades, dtype=np.float64)
    n_trades = len(trades)
    if n_trades &amp;lt; 10:
        raise ValueError(&quot;At least 10 trades required for meaningful permutation testing.&quot;)

    rng = np.random.default_rng(seed)
    
    # 1. Broadcast and independently shuffle across simulations (without replacement)
    sim_matrix = np.tile(trades, (num_simulations, 1))
    shuffled_trades = rng.permuted(sim_matrix, axis=1)

    # 2. Vectorized cumulative equity trajectories
    cumulative_pnl = np.cumsum(shuffled_trades, axis=1)
    initial_col = np.full((num_simulations, 1), initial_capital)
    equity_paths = np.hstack([initial_col, initial_capital + cumulative_pnl])

    # 3. Peak equity and path drawdowns
    running_peaks = np.maximum.accumulate(equity_paths, axis=1)
    drawdowns_dollar = running_peaks - equity_paths
    drawdowns_pct = (drawdowns_dollar / running_peaks) * 100.0

    # 4. Max drawdown per trajectory
    max_mdd_pct = np.max(drawdowns_pct, axis=1)

    # 5. Ruin evaluation (breaching floor at any point in time)
    min_equity_per_path = np.min(equity_paths, axis=1)
    ruin_paths = np.sum(min_equity_per_path &amp;lt;= ruin_capital)
    prob_ruin = (ruin_paths / num_simulations) * 100.0

    return {
        &quot;n_trades&quot;: n_trades,
        &quot;initial_capital&quot;: initial_capital,
        &quot;ruin_capital&quot;: ruin_capital,
        &quot;nominal_net_pnl&quot;: float(round(np.sum(trades), 2)),
        &quot;num_simulations&quot;: num_simulations,
        &quot;prob_ruin_pct&quot;: float(round(prob_ruin, 2)),
        &quot;mdd_p05_pct&quot;: float(round(np.percentile(max_mdd_pct, 5), 2)),
        &quot;mdd_p50_median_pct&quot;: float(round(np.percentile(max_mdd_pct, 50), 2)),
        &quot;mdd_p95_worst_pct&quot;: float(round(np.percentile(max_mdd_pct, 95), 2)),
        &quot;falsification_verdict&quot;: &quot;REJECT_EXCESSIVE_RUIN&quot; if prob_ruin &amp;gt; 5.0 else &quot;PASS_ROBUST_SEQUENCE&quot;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-empirical-case-study-the-profitable-strategy-that-blows-up&quot;&gt;4. Empirical Case Study: The &quot;Profitable&quot; Strategy That Blows Up&lt;/h3&gt;
&lt;p&gt;Consider a 100-trade sample with a &lt;strong&gt;52% win rate&lt;/strong&gt; and positive net expectation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;52 winning trades (mean profit ~\$170)&lt;/li&gt;
&lt;li&gt;48 losing trades (mean loss ~-\$170, with occasional -\$350 tail losses)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nominal Net P&amp;amp;L:&lt;/strong&gt; &lt;code&gt;+$505.39&lt;/code&gt; on a &lt;code&gt;\$10,000&lt;/code&gt; account (profitable on paper).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When we execute &lt;code&gt;run_monte_carlo_permutation&lt;/code&gt; over 5,000 shuffles:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Empirical verification run
result = run_monte_carlo_permutation(
    trades=trades_sample,
    initial_capital=10000.0,
    ruin_capital=9000.0, # 10% maximum drawdown ceiling
    num_simulations=5000,
    seed=42
)

print(result)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;exact-output-&quot;&gt;Exact Output:&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;n_trades&quot;: 100,
  &quot;initial_capital&quot;: 10000.0,
  &quot;ruin_capital&quot;: 9000.0,
  &quot;nominal_net_pnl&quot;: 505.39,
  &quot;num_simulations&quot;: 5000,
  &quot;prob_ruin_pct&quot;: 42.64,
  &quot;mdd_p05_pct&quot;: 11.3,
  &quot;mdd_p50_median_pct&quot;: 16.77,
  &quot;mdd_p95_worst_pct&quot;: 25.5,
  &quot;falsification_verdict&quot;: &quot;REJECT_EXCESSIVE_RUIN&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-institutional-reality-&quot;&gt;The Institutional Reality:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Even though the original single backtest ended with &lt;strong&gt;+$505.39 net profit&lt;/strong&gt;, &lt;strong&gt;42.64% of all possible alternate histories breached the 10% drawdown barrier&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In 95% of histories, the drawdown reached up to &lt;strong&gt;25.50%&lt;/strong&gt;—more than double the allowable risk budget.&lt;/li&gt;
&lt;li&gt;Without Monte Carlo permutation, a developer deploying this system would falsely attribute failure to &quot;market regime change&quot; or &quot;bad luck&quot;, when in reality the strategy was statistically insolvent against basic sequence risk from day one.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-methodological-boundaries-what-monte-carlo-cannot-do-&quot;&gt;5. Methodological Boundaries (What Monte Carlo Cannot Do)&lt;/h3&gt;
&lt;p&gt;A rigorous quant must understand the falsification limits of permutation testing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Serial Autocorrelation Breakdown:&lt;/strong&gt; Permutation randomly destroys the temporal ordering of trades. If your strategy has positive autocorrelation in losses (e.g., clustered losses during high-volatility macro announcements), permutation may underestimate clustering severity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Out-of-Distribution Shocks:&lt;/strong&gt; Shuffling only explores combinations of &lt;em&gt;events that already occurred&lt;/em&gt;. It cannot simulate a 5-sigma liquidity flash crash if none was present in your historical log.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Journal vs. Market:&lt;/strong&gt; Monte Carlo evaluates the statistical fragility of the &lt;em&gt;trade sequence&lt;/em&gt;, not the underlying market microstructure.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-open-source-implementation-amp-references&quot;&gt;6. Open Source Implementation &amp;amp; References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scientific DOI:&lt;/strong&gt; Reproducible quant code registered under CERN Zenodo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://doi.org/10.5281/zenodo.22012203&quot;&gt;&lt;code&gt;10.5281/zenodo.22012203&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/guetaquant-byte/guetaquant-tools&quot;&gt;&lt;code&gt;guetaquant-byte/guetaquant-tools&lt;/code&gt;&lt;/a&gt; (AGPLv3).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interactive Tool:&lt;/strong&gt; Browser-based Monte Carlo permutation is integrated into the client-side &lt;a rel=&quot;nofollow&quot; href=&quot;https://guetaquant.com/journal/&quot;&gt;Local-First Trading Journal&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In-Depth Study:&lt;/strong&gt; Full mathematical derivation and percentile bands at &lt;a rel=&quot;nofollow&quot; href=&quot;https://guetaquant.com/blog/monte-carlo-trading/&quot;&gt;Gueta Quant Monte Carlo Analysis&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Aviso Regulatorio (SFC Colombia — Decreto 2555 de 2010): Este artículo tiene un propósito 100% pedagógico, educativo y de investigación en ingeniería de software financiero. Gueta Quant no presta asesoría financiera ni emite señales de inversión.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;By &lt;strong&gt;Mahdi Goodarzi&lt;/strong&gt; (&lt;a rel=&quot;nofollow&quot; href=&quot;https://g.dev/mahdigoodarzi&quot;&gt;g.dev/mahdigoodarzi&lt;/a&gt;), Founder of Gueta Quant.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28876/monte-carlo-permutation-testing-python-how-random-trade-order-falsifies-your-equity-curve</guid>
<pubDate>Mon, 28 Sep 2026 06:17:46 +0000</pubDate>
</item>
<item>
<title>$1.45 Billion in Federal AI Contracts, and the Ledger Can Account for 17% of It</title>
<link>https://coderlegion.com/28622/1-45-billion-in-federal-ai-contracts-and-the-ledger-can-account-for-17-of-it</link>
<description>&lt;p&gt;On October 31, 2019, the Department of Commerce signed an award with Accenture Federal Services to &quot;improve patent search with artificial intelligence.&quot; USAspending.gov, the federal government's public ledger of what it spends, records the award at $44,076,744.60. Its period of performance ended on April 30, 2023. The same ledger has a column for what was actually paid on the award. That column reads 0.00.&lt;/p&gt;
&lt;p&gt;Not blank. Zero, to the cent, on a contract that finished more than three years ago.&lt;/p&gt;
&lt;p&gt;One row is an anecdote. So I pulled every federal contract award whose description contains the words &quot;artificial intelligence&quot; and that was signed or active between October 1, 2024, and September 11, 2026. There are 491 of them, across 29 awarding agencies, carrying $1,449,720,817.76 in obligations. The Department of Defense holds $937.6 million of that, just under two thirds. Then I added up the paid column, the one the Accenture row leaves at zero. Across all 491 awards it comes to $249,293,997.21.&lt;/p&gt;
&lt;p&gt;That is 17.2 percent. For the other 83 percent of the money, the public record of federal AI spending cannot say what has been paid.&lt;/p&gt;
&lt;h3 id=&quot;the-objection-and-what-survives-it&quot;&gt;The objection, and what survives it&lt;/h3&gt;
&lt;p&gt;The obvious answer is that obligations are not payments. An obligation is a promise: the government has committed the money to a contract. An outlay is the check. A contract signed last spring has had no time to bill, so its outlay is zero and there is nothing wrong with that. If the population were mostly new awards, a low paid fraction would be a description of the calendar, not of the ledger.&lt;/p&gt;
&lt;p&gt;It is partly the calendar. Of the 491 awards, 184 started in 2025 and 99 in 2026. Nobody should expect those to have paid out. So the honest test is not the total. It is the shape of the column, award by award, and in particular what it says about the awards that have had years to bill.&lt;/p&gt;
&lt;p&gt;Here is the shape. USAspending's award-level outlay field answers the question &quot;what was paid&quot; in five different ways on this population.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;what the outlay column says &lt;/th&gt;&lt;th&gt;awards &lt;/th&gt;&lt;th&gt;obligations &lt;/th&gt;&lt;th&gt;share of dollars&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;the field is absent entirely &lt;/td&gt;&lt;td&gt;269 &lt;/td&gt;&lt;td&gt;$488,560,429 &lt;/td&gt;&lt;td&gt;33.7%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;exactly zero &lt;/td&gt;&lt;td&gt;98 &lt;/td&gt;&lt;td&gt;$409,146,653 &lt;/td&gt;&lt;td&gt;28.2%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;a partial figure &lt;/td&gt;&lt;td&gt;77 &lt;/td&gt;&lt;td&gt;$389,522,445 &lt;/td&gt;&lt;td&gt;26.9%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;equal to the obligation, to the cent &lt;/td&gt;&lt;td&gt;45 &lt;/td&gt;&lt;td&gt;$160,754,616 &lt;/td&gt;&lt;td&gt;11.1%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;more than the obligation &lt;/td&gt;&lt;td&gt;2 &lt;/td&gt;&lt;td&gt;$1,736,674 &lt;/td&gt;&lt;td&gt;0.1%&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The first two rows are the finding. A third of the dollars sit on awards where the ledger has no outlay field at all, not a zero, nothing. Another 28 percent sit on awards where the field exists and reads exactly zero. Only the bottom three rows, 38 percent of the money, carry a number that could be read as a payment, and the last of those rows is a number that cannot be right.&lt;/p&gt;
&lt;h3 id=&quot;the-zeros-that-had-time&quot;&gt;The zeros that had time&lt;/h3&gt;
&lt;p&gt;Take only the awards that began before 2024, which have had at least twenty months inside this window to bill. Twenty-four of them report exactly zero outlays, with $100.9 million obligated between them. The Accenture patent-search award is the largest. Another Accenture award for the same agency, $13.3 million, ran from May 2023 to June 2024 and reports zero. A DARPA award to Kitware, $4.8 million, ran from November 2021 to the end of 2024 and reports zero. Two more DARPA awards from the same week, to SRI International and to RTX's BBN unit, together $8.8 million, report zero. A MITRE award for Commerce, $4.2 million, ran from September 2021 to November 2024 and reports zero.&lt;/p&gt;
&lt;p&gt;A further 24 pre-2024 awards, $47.1 million, have no outlay field at all.&lt;/p&gt;
&lt;p&gt;These are not contracts that have not had time. Several of them are over. The work was, by the government's own dates, delivered and closed, and the column that is supposed to record the money going out records either nothing or none.&lt;/p&gt;
&lt;p&gt;The largest single award in the population points the same way from a different angle. ECS Federal's Army contract to build prototype AI and machine-learning algorithms, signed in April 2020 and running to March 2027, carries $120,575,059.35 in obligations and $4,671,161.19 in outlays. That is 3.9 percent, six years into a seven-year contract with 47 subawards under it. Either almost nothing has been paid on the Army's biggest named AI contract, or the column does not know.&lt;/p&gt;
&lt;h3 id=&quot;the-documentation-dates-the-problem&quot;&gt;The documentation dates the problem&lt;/h3&gt;
&lt;p&gt;USAspending is candid about the mechanism, and it puts a date on it. Its &quot;About the Data&quot; guide says: &quot;Beginning in Fiscal Year 2022, all agencies are required to submit their financial data on a monthly basis, including outlay data for award spending. Any outlay data before this period were optional for agencies to report, and thus may be incomplete.&quot;&lt;/p&gt;
&lt;p&gt;Read that against the table. Optional reporting before fiscal 2022 explains an empty field on a 2019 award. It does not explain a zero on an award whose entire performance ran after the requirement took effect, and it does not explain the 2025 award to ECS Federal, $72.8 million for AI research and deployment, which sits in the mandatory era with an outlay of 0.00. Nor does the calendar. The requirement has been in force for four fiscal years. The awards with the largest gaps between promise and recorded payment are the ones that spent the most time inside it.&lt;/p&gt;
&lt;p&gt;The site's methodology page describes how this happens. Agencies report award-level obligations and outlays in a file the system calls File C, and that file is linked to the contract records that give an award its description and its recipient. When the two do not link, the award still appears in searches, with its obligation, and the outlay side is simply not there. The Government Accountability Office reviewed federal spending-transparency data quality in 2022 and found the agency plans for it had gaps, outlay tracking among them. None of this is hidden. It is documented, dated, and then routinely ignored by everyone who quotes the obligation figure as if it were the spend.&lt;/p&gt;
&lt;h3 id=&quot;two-awards-that-paid-more-than-they-promised&quot;&gt;Two awards that paid more than they promised&lt;/h3&gt;
&lt;p&gt;The cheapest demonstration that the outlay column is not a clean measurement is the last row of the table. Aperio Global's award 70SBUR24F00000223 shows $1,538,850.00 obligated and $1,539,034.52 paid. HCC Consulting's award 72MC1025C00003 shows $197,824.49 obligated and $198,319.43 paid. Between them, the ledger reports $679.46 in payments on money that was never committed.&lt;/p&gt;
&lt;p&gt;Small amounts. Possibly a modification recorded on one side and not the other. But a column that can exceed its own ceiling is a column being filled by hand, from different systems, on different schedules, and that is the same column the 17.2 percent comes from. The 45 awards where outlays equal obligations to the cent deserve the same suspicion in the other direction. Tuknik Government Services' Interior award for AI support to the Pentagon's Joint Artificial Intelligence Center shows $106,725,460.68 obligated and $106,725,460.68 paid. That can be true. It is also exactly what a field looks like when someone copied the obligation into it.&lt;/p&gt;
&lt;h3 id=&quot;the-arithmetic-corrected-itself-once&quot;&gt;The arithmetic corrected itself once&lt;/h3&gt;
&lt;p&gt;A piece about a ledger that cannot add up should show its own sums being checked. The first pass at these numbers used the 501 rows the search endpoint returned. Only 491 of them were distinct awards. Ten were repeats of rows already returned on an earlier page, a Stevens Institute award among them, and the uncorrected obligation total was $1,496,023,651.74, which is $46.3 million too high. Every figure here is computed with the duplicates dropped on the award's own identifier, and the count of dropped rows is reported rather than quietly applied, so anyone re-running the query can tell whether the endpoint's behaviour has changed rather than inheriting a silent fix.&lt;/p&gt;
&lt;p&gt;The correction also moved a number in the research file this essay was written from. That file counted 26 pre-2024 awards with zero outlays and $111.8 million between them; on the deduplicated table the count is 24 and the sum $100.9 million. The essay carries the deduplicated figures, and the difference is two rows that the API served twice.&lt;/p&gt;
&lt;h3 id=&quot;what-the-number-is-and-what-it-is-being-read-as&quot;&gt;What the number is, and what it is being read as&lt;/h3&gt;
&lt;p&gt;Every procurement announcement, agency press release and chart of &quot;federal AI spending&quot; uses obligations, because obligations are the number that exists for every award. The column that answers the question a taxpayer actually asks, what has been paid, is populated well enough on this population to answer for 17.2 percent of the money, and for nearly a third of it the field does not exist.&lt;/p&gt;
&lt;p&gt;That matters more for artificial intelligence than for most categories, because these are the contracts cited as evidence of a federal AI build-out. A $1.45 billion figure that is 62 percent unaccounted for in the payment column is a measurement of intent. It is being read as a measurement of activity. The distinction can be recovered, but only by reading the column that is mostly empty, and by saying which awards it is empty on.&lt;/p&gt;
&lt;p&gt;None of this says the money was wasted or the work not done. An absent outlay is a reporting fact. The Accenture patent-search contract may have delivered exactly what it promised; the ledger has no way to tell you, and neither do I. What the record supports is narrower and, I think, more useful: when someone tells you how much the federal government is spending on artificial intelligence, ask which column they read.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;The federal award figures in this piece were computed from the USAspending API v2 on 12 September 2026 and rechecked from the saved response before writing: 491 distinct contract awards, $1,449,720,817.76 obligated, $249,293,997.21 in the award-level outlay column. The query is the one in Sources below, so anyone can re-run it and compare. Two controls ran beside it: a positive one, in which the ECS Federal award must appear at its known obligation, and a negative one, in which a nonsense description filter must return nothing. Ten duplicate rows were dropped and counted. The population is awards whose description contains &quot;artificial intelligence&quot;; it will include boilerplate mentions and exclude AI work described as machine learning or autonomy, and the claim is about what the ledger says of the awards that call themselves AI, not about all of them.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Part of &lt;strong&gt;Where the Number Came From&lt;/strong&gt;, on how a published number is a fact about the way it was measured: &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/stanford-says-12-to-66-but-12-of-what/&quot;&gt;Stanford says 12% to 66%, but 12% of what?&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/the-safety-score-is-a-fact-about-the-test-rig/&quot;&gt;The safety score is a fact about the test rig&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/95-80-42-tracing-the-2026-ai-failure-statistics/&quot;&gt;Tracing the 2026 AI-failure statistics to a primary&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sources:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;USAspending API v2, &lt;code&gt;POST /api/v2/search/spending_by_award/&lt;/code&gt;, filters: award types A, B, C, D; time period 2024-10-01 to 2026-09-11; description &quot;artificial intelligence&quot;. Run 12 September 2026. &lt;a rel=&quot;nofollow&quot; href=&quot;https://api.usaspending.gov/api/v2/search/spending_by_award/&quot;&gt;https://api.usaspending.gov/api/v2/search/spending_by_award/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;USAspending API v2, &lt;code&gt;GET /api/v2/awards/CONT_AWD_W911QX20C0023_9700_-NONE-_-NONE-/&lt;/code&gt; (the ECS Federal award detail, including the subaward count and period of performance).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;USAspending.gov, &lt;em&gt;About the Data&lt;/em&gt;, last updated 19 November 2025, the section on agency financial data submissions (the FY2022 monthly outlay requirement and the note that earlier outlay data were optional). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.usaspending.gov/data/about-the-data-download.pdf&quot;&gt;https://www.usaspending.gov/data/about-the-data-download.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;USAspending.gov, &lt;em&gt;Data Sources &amp;amp; Methodology for Agency Submission Statistics&lt;/em&gt; (File C as the award-level obligation and outlay file, and unlinked File C awards). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.usaspending.gov/submission-statistics/data-sources&quot;&gt;https://www.usaspending.gov/submission-statistics/data-sources&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;U.S. Government Accountability Office, GAO-22-105427, &lt;em&gt;Federal Spending Transparency&lt;/em&gt; (2022). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.gao.gov/assets/gao-22-105427.pdf&quot;&gt;https://www.gao.gov/assets/gao-22-105427.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Data &amp; AI</category>
<guid isPermaLink="true">https://coderlegion.com/28622/1-45-billion-in-federal-ai-contracts-and-the-ledger-can-account-for-17-of-it</guid>
<pubDate>Fri, 25 Sep 2026 15:09:49 +0000</pubDate>
</item>
<item>
<title>Let the LLM Write the Parser, Never Run It: How labparse Handles Unknown Instrument Files</title>
<link>https://coderlegion.com/28481/let-the-llm-write-the-parser-never-run-it-how-labparse-handles-unknown-instrument-files</link>
<description>&lt;p&gt;If you have ever written a one off script to read a CSV that was not really a CSV, this one is for you. It is a small open source Python package built around a pattern more of us should borrow: the model writes code once, and deterministic code does every run after that.&lt;/p&gt;
&lt;h3 id=&quot;the-problem-every-instrument-has-its-own-export-format&quot;&gt;The Problem: Every Instrument Has Its Own Export Format&lt;/h3&gt;
&lt;p&gt;Lab instruments are a great stress test for parsers. A plate reader writes a metadata header, then a grid of wells per reading cycle, then maybe a temperature column, all in a layout that changes between vendors and sometimes between firmware versions. Most labs end up with a folder of throwaway scripts, one per instrument, each quietly breaking the next time someone changes an export setting.&lt;/p&gt;
&lt;h3 id=&quot;recipes-instead-of-scripts&quot;&gt;Recipes Instead of Scripts&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.learnhowtoscience.com/ai-science-tools/labparse.php&quot;&gt;labparse&lt;/a&gt; splits the job in two. A format is described by a recipe, a small JSON file that says where the header ends, how the grid is laid out, and how time and cycle numbers are read. A deterministic engine executes the recipe. It ships with recipes for Tecan ASCII exports, SoftMax Pro and SpectraMax kinetic files, generic wide and long CSV or TSV tables, and Excel workbooks with a plate grid.&lt;/p&gt;
&lt;p&gt;Every parse produces the same tidy schema whatever the instrument: &lt;code&gt;well&lt;/code&gt;, &lt;code&gt;row&lt;/code&gt;, &lt;code&gt;col&lt;/code&gt;, &lt;code&gt;cycle&lt;/code&gt;, &lt;code&gt;time_s&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt;, plus any extra columns like temperature.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import labparse
result = labparse.parse(&quot;growth.asc&quot;)
result.data        # tidy pandas DataFrame
result.wide()      # one column per well
result.save_csv(&quot;tidy.csv&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;where-the-llm-comes-in-and-where-it-does-not&quot;&gt;Where the LLM Comes In, and Where It Does Not&lt;/h3&gt;
&lt;p&gt;When labparse meets a format it has no recipe for, it shows the raw file to your own model once and asks it to write a new recipe. That is the only job the model gets. The recipe is then run by the same engine against your actual file, and it is only accepted if the parse succeeds. A wrong recipe is rejected and retried, so a bad guess can never turn into silently wrong numbers.&lt;/p&gt;
&lt;p&gt;Accepted recipes are cached in your home directory, so each new format costs at most one model call ever, and cached formats parse offline. Recipes are plain JSON, which means you can read them, fix them by hand, and share them with the rest of your lab.&lt;/p&gt;
&lt;p&gt;For the model it checks, in order: the Claude CLI, the Codex CLI, an Anthropic API key, then an OpenAI API key, and a base URL setting points it at any OpenAI compatible server, local models included. With no model configured, everything with a bundled or cached recipe still works, and an unknown format gets a clear message instead of a guess.&lt;/p&gt;
&lt;h3 id=&quot;why-this-pattern-travels&quot;&gt;Why This Pattern Travels&lt;/h3&gt;
&lt;p&gt;The part worth taking to other projects is the split. The model proposes, the deterministic engine disposes, and your data only ever flows through code you can inspect. It fits log formats, bank statement exports, legacy reports, anything where the variety of inputs is the real problem and the correctness of the output is not negotiable.&lt;/p&gt;
&lt;h3 id=&quot;the-takeaway&quot;&gt;The Takeaway&lt;/h3&gt;
&lt;p&gt;Let the LLM write the parser, verify it against real input, cache the result, and never let it touch the numbers directly. labparse is MIT licensed, installs with &lt;code&gt;pip install labparse&lt;/code&gt;, and the project page above links to the source and issue tracker.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28481/let-the-llm-write-the-parser-never-run-it-how-labparse-handles-unknown-instrument-files</guid>
<pubDate>Thu, 24 Sep 2026 12:58:05 +0000</pubDate>
</item>
<item>
<title>Two Complaints in Two Years: New York City's AI Hiring Law Made a Record Nobody Can Read</title>
<link>https://coderlegion.com/28426/two-complaints-in-two-years-new-york-citys-ai-hiring-law-made-a-record-nobody-can-read</link>
<description>&lt;p&gt;&lt;em&gt;The state Comptroller found two complaints in twenty-four months. Cornell's team found 14 audit reports among 267 employers. Both measurements hit the same wall: a disclosure rule whose scope is set by the discloser produces a record that cannot tell silence from exemption.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Between July 2023 and June 2025, New York City's Department of Consumer and Worker Protection received two complaints about employers using automated hiring tools without the bias audit the city requires. Two, in twenty-four months. The count comes from the New York State Comptroller, whose auditors spent that window's records deciding whether the department had &quot;designed and implemented an effective system to enforce compliance with Local Law 144.&quot; Their answer, published on December 2, 2025, was that the department's complaint process &quot;is ineffective in ensuring that all complaints related to non-compliance with LL144 are routed to DCWP.&quot;&lt;/p&gt;
&lt;p&gt;The auditors then did the thing a complaint count cannot do for itself: they went and looked. The department had surveyed 32 companies about their use of these tools and found one non-compliance issue. The auditors examined the same 32 companies and found 17 potential instances. And the department's own words for the limit it works under, quoted in the audit, are these: &quot;If an employer does not take these steps, it's difficult to identify non-compliance.&quot;&lt;/p&gt;
&lt;p&gt;That sentence is the subject of this piece. It sounds like an admission of under-resourcing. It is a description of the law's design, and it means the near-empty record the city has built cannot be read as good news or as bad.&lt;/p&gt;
&lt;h3 id=&quot;what-the-law-asked-for&quot;&gt;What the law asked for&lt;/h3&gt;
&lt;p&gt;Local Law 144 of 2021 prohibits employers and employment agencies from using an automated employment decision tool unless the tool &quot;has been subject to a bias audit within one year of the use of the tool, information about the bias audit is publicly available, and certain notices have been provided to employees or job candidates.&quot; Enforcement began on July 5, 2023. The penalties are small per unit and multiply by time: not more than $500 for a first violation, $500 to $1,500 for each subsequent one, and each day a tool is used in violation counts as a separate violation.&lt;/p&gt;
&lt;p&gt;Read the requirement again and notice what kind of law it is. It does not tell employers to do something to candidates. It tells them to publish something about themselves. The audit summary must be public. The notice must be given. Compliance leaves a paper trail by definition, which means the law creates its own public record, and anyone with a browser can go and count what is in it.&lt;/p&gt;
&lt;h3 id=&quot;the-first-count-2023&quot;&gt;The first count, 2023&lt;/h3&gt;
&lt;p&gt;Somebody did. In the autumn of 2023 a team from Cornell's Citizens and Technology Lab, Data &amp;amp; Society and Consumer Reports recruited 155 student investigators and sent them through employer career sites between October 24 and November 9, modelling what a job seeker would see. The paper that came out of it, &quot;Null Compliance: NYC Local Law 144 and the Challenges of Algorithm Accountability,&quot; presented at FAccT 2024, reports in its section 6.1: &quot;Among 267 employers posting open jobs in NYC, we found 14 audit reports and 12 transparency notices.&quot;&lt;/p&gt;
&lt;p&gt;Fourteen of 267 is 5.2 percent. Twelve of 267 is 4.5 percent. The paper's own abstract gives a different figure, 18 audit reports among 391 employers; that is the full set of employers the students recorded rather than the subset with live New York postings, and it works out to 4.6 percent. The two figures are not rival estimates of one quantity, and I am not going to average them. Keep the denominators attached. That habit is going to matter in a moment.&lt;/p&gt;
&lt;p&gt;So a few months after enforcement began, roughly one employer in twenty had put anything in it. The obvious reading is that nineteen in twenty were breaking the law. The authors refuse that reading, and the refusal is their contribution.&lt;/p&gt;
&lt;h3 id=&quot;null-compliance-is-not-non-compliance&quot;&gt;Null compliance is not non-compliance&lt;/h3&gt;
&lt;p&gt;The paper names the state it found: &quot;Null compliance describes a state in which the absence of evidence of compliance cannot be ascertained as non-compliance because the investigator lacks the information to determine if the regulated party's actions or products are in scope of the regulation.&quot;&lt;/p&gt;
&lt;p&gt;The mechanism sits in the law's trigger. Local Law 144 covers tools that substantially assist or replace the human decision. Whether a given piece of software does that is not a property of the software. It is a property of how much weight the employer chooses to give it, which the paper puts plainly: &quot;Two employers that use identical models could grant those models different degrees of influence over their respective hiring decisions—an entirely organizational matter—and thereby have different regulatory statuses.&quot; The regulated party decides whether it is in the population.&lt;/p&gt;
&lt;p&gt;The survey the researchers ran beside the site walk shows what that decision looks like from the inside. Of the 26 firms that answered, 23 said the law did not apply to them. Eighty-eight percent of a small, self-selected group, so not a rate to build on, but a clean illustration. An employer that posts nothing may be using no such tool, or using one it has decided does not substantially assist anyone, or using one it should have audited and did not. From the career page all three look identical. The absence of a disclosure is the same absence in every case.&lt;/p&gt;
&lt;p&gt;So the 5.2 percent is not a compliance rate. It is the share of employers who both fell inside the law by their own reckoning and did what it asked. Nobody outside those employers knows the size of the first group. The denominator that would make the number readable is the one number nobody holds.&lt;/p&gt;
&lt;h3 id=&quot;the-second-count-2025-by-the-state&quot;&gt;The second count, 2025, by the state&lt;/h3&gt;
&lt;p&gt;Two years later the Comptroller's office measured the same record from the other side, the enforcer's, and hit the same wall. The department's model, in the audit's words, is &quot;stakeholder education combined with complaint-based enforcement.&quot; A complaint-based design asks candidates to report a violation. The violation here is an absence: an audit summary that was never posted, a notice that was never given. A candidate who was screened by an unaudited tool and never told is, by construction, a person who does not know it happened. The two complaints in two years are not a measure of how often the law was broken. They are closer to a count of how often a violation of this kind is visible to the person it happened to.&lt;/p&gt;
&lt;p&gt;That is why the department's sentence about difficulty is not a resourcing complaint. If an employer does not post, the employer is either outside the law or breaking it, and from the outside there is no third thing to look at. The auditors' 17 potential instances against the department's one, in the same 32 companies, is the only place in either measurement where a second, closer look changed the count. It took the state's access to get it, and it is still labelled potential.&lt;/p&gt;
&lt;p&gt;Put the two measurements side by side. Researchers walking the public record in 2023 found it nearly empty and said they could not tell why. State auditors reading the enforcement record in 2025 found it nearly empty and, in effect, said the same. Neither found that the law is working. Neither found that it is failing. Both found that the instrument they were holding could not answer the question, and it is the same instrument: a record whose population is defined by the people in it.&lt;/p&gt;
&lt;h3 id=&quot;what-would-make-the-zero-legible&quot;&gt;What would make the zero legible&lt;/h3&gt;
&lt;p&gt;None of this says employers are breaking the law, and I want to be exact about that, because converting &quot;null compliance&quot; into &quot;non-compliance&quot; is the error the paper exists to name. It also does not say the law failed; the state audit examined the department's enforcement system and addressed its six recommendations to the department, not to the statute. And I have counted nothing myself. The counts are the researchers' and the auditors'. The only arithmetic here is division, and the script that does it ships beside this essay with each figure's locator.&lt;/p&gt;
&lt;p&gt;What the two measurements do establish is a condition. A disclosure rule whose scope is decided by the party disclosing produces a record that cannot distinguish silence from exemption. Any count taken from that record, high or low, inherits the ambiguity. Two complaints, fourteen audits, one issue found, seventeen suspected: every one of those numbers has the same missing denominator, which is the number of employers who were actually inside the law.&lt;/p&gt;
&lt;p&gt;A zero becomes readable when someone other than the regulated party defines the population. A register of employers using such tools, or a duty to declare use rather than only to audit it, would turn the next walk through the career sites into a measurement. Whether New York should want either is a policy question this piece is not placed to settle. What can be said is narrower and holds either way. The city built a public record to find out whether hiring software was being checked for bias. Two years and two instruments in, the record's clearest finding is that it cannot be read.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Reproduction script, published with this piece and runnable with no API key or network access: &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/code/rates_c5500.py&quot;&gt;rates_c5500.py&lt;/a&gt;. Every percentage and ratio above is computed there from the published counts, each row carrying its source locator, and the 14-of-267 and 18-of-391 figures are kept as separate populations on purpose.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sources: Lucas Wright, Roxana Mika Muenster, Briana Vecchione, Tianyao Qu, Pika (Senhuang) Cai, Alan Smith, Jacob Metcalf and J. Nathan Matias, &lt;a rel=&quot;nofollow&quot; href=&quot;https://arxiv.org/abs/2406.01399&quot;&gt;&quot;Null Compliance: NYC Local Law 144 and the Challenges of Algorithm Accountability,&quot;&lt;/a&gt; FAccT 2024, arXiv:2406.01399, DOI 10.1145/3630106.3658998, sections 2.2, 3, 5.1 and 6.1; Office of the New York State Comptroller, &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.osc.ny.gov/state-agencies/audits/2025/12/02/enforcement-local-law-144-automated-employment-decision-tools&quot;&gt;&quot;Enforcement of Local Law 144 – Automated Employment Decision Tools,&quot;&lt;/a&gt; audit published December 2, 2025, covering July 2023 through June 2025; NYC Department of Consumer and Worker Protection, &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.nyc.gov/site/dca/about/automated-employment-decision-tools.page&quot;&gt;&quot;Automated Employment Decision Tools&quot;&lt;/a&gt; (the requirement and the July 5, 2023 enforcement date); New York City Administrative Code § 20-872 (the penalty schedule); Citizens and Technology Lab, &quot;Studying How Employers Comply with NYC's New Hiring Algorithm Law&quot; (the 18-of-391 figure, cited as the write-up's own).&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;a-disclosure-record-is-only-as-readable-as-its-population&quot;&gt;A disclosure record is only as readable as its population&lt;/h4&gt;
&lt;p&gt;Local Law 144 asks employers to publish something about themselves, then lets each employer&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    decide whether it is covered. The result is a record where an absence proves nothing. The same
    shape shows up wherever an agent's behaviour is self-reported: a claim with no population
    behind it. Chain of Consciousness keeps a record of what an agent actually did, over traffic
    someone else can check, so a rate you publish carries the denominator it was measured over.
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;pip install chain-of-consciousness
npm install chain-of-consciousness
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or start without installing anything: &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/hosted-coc/&quot;&gt;Hosted Chain of Consciousness&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/&quot;&gt;← Back to blog&lt;/a&gt;&lt;/p&gt;
</description>
<category>Data &amp; AI</category>
<guid isPermaLink="true">https://coderlegion.com/28426/two-complaints-in-two-years-new-york-citys-ai-hiring-law-made-a-record-nobody-can-read</guid>
<pubDate>Thu, 24 Sep 2026 05:51:26 +0000</pubDate>
</item>
<item>
<title>Neural Networks: Inspired by the Brain, Built for Data</title>
<link>https://coderlegion.com/28356/neural-networks-inspired-by-the-brain-built-for-data</link>
<description>&lt;p&gt;When I first started learning about neural networks, I found the idea both fascinating and slightly intimidating.&lt;/p&gt;
&lt;p&gt;The phrase &lt;em&gt;“neural network”&lt;/em&gt; makes it sound like something extremely complex, and honestly, once you start looking at layers, weights, activation functions, forward propagation, backpropagation, and optimization, it can become overwhelming pretty quickly.&lt;/p&gt;
&lt;p&gt;But beneath all of that mathematics and code is a surprisingly simple idea:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;A neural network learns patterns from data by adjusting itself based on its mistakes.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That idea is what made neural networks interesting to me.&lt;/p&gt;
&lt;p&gt;As I continue exploring data science and machine learning, understanding neural networks has helped me see machine learning from a different perspective. Instead of simply telling a computer what rules to follow, we can give it data and allow it to learn patterns that may be difficult for us to define manually.&lt;/p&gt;
&lt;p&gt;In this article, I'll break down where the inspiration for neural networks came from, what they are, and the major components that make them work.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;where-did-neural-networks-come-from-&quot;&gt;Where Did Neural Networks Come From?&lt;/h3&gt;
&lt;p&gt;The inspiration behind artificial neural networks comes from something we already have:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;the human brain.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Our brains contain billions of neurons that communicate with one another. A biological neuron receives signals, processes them, and passes a signal forward when certain conditions are met.&lt;/p&gt;
&lt;p&gt;Artificial neural networks don't replicate the human brain perfectly. Instead, they borrow a simplified idea from how biological neurons work.&lt;/p&gt;
&lt;p&gt;A biological neuron can be thought of roughly as:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;receive signals → process them → produce a response&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An artificial neuron follows a similar conceptual process:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;receive inputs → calculate a weighted sum → apply an activation function → produce an output&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This was one of the things I found interesting when learning about neural networks.&lt;/p&gt;
&lt;p&gt;The goal isn't to recreate the brain inside a computer.&lt;/p&gt;
&lt;p&gt;The goal is to take a useful idea from biology and turn it into a mathematical model that can learn from data.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;so-what-exactly-is-a-neural-network-&quot;&gt;So, What Exactly Is a Neural Network?&lt;/h3&gt;
&lt;p&gt;A neural network is a machine learning model made up of interconnected nodes, commonly called &lt;strong&gt;neurons&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;These neurons are organized into layers.&lt;/p&gt;
&lt;p&gt;A basic neural network usually contains:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Input layer&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One or more hidden layers&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Output layer&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A simple representation looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Input Layer        Hidden Layer        Output Layer

   x₁  ─────────►    ○
                    │
   x₂  ─────────►    ○ ─────────►     ŷ
                    │
   x₃  ─────────►    ○
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The input layer receives the data.&lt;/p&gt;
&lt;p&gt;The hidden layers transform the data and learn patterns.&lt;/p&gt;
&lt;p&gt;The output layer produces the final prediction.&lt;/p&gt;
&lt;p&gt;The interesting part is that we don't manually program the network to recognize every possible pattern.&lt;/p&gt;
&lt;p&gt;Instead, the network learns the parameters needed to recognize those patterns from examples.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-the-input-layer&quot;&gt;1. The Input Layer&lt;/h3&gt;
&lt;p&gt;Everything starts with the input.&lt;/p&gt;
&lt;p&gt;Suppose we want to predict whether a student is likely to pass an exam.&lt;/p&gt;
&lt;p&gt;Our dataset might contain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hours studied&lt;/li&gt;
&lt;li&gt;Attendance&lt;/li&gt;
&lt;li&gt;Previous test scores&lt;/li&gt;
&lt;li&gt;Assignment completion&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These become our input features.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Hours Studied     = 6
Attendance        = 85%
Previous Score    = 72
Assignments       = 9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The neural network receives these values through the input layer.&lt;/p&gt;
&lt;p&gt;One important thing I learned is that neural networks generally work best when the input data has been appropriately prepared.&lt;/p&gt;
&lt;p&gt;That can involve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Handling missing values&lt;/li&gt;
&lt;li&gt;Encoding categorical variables&lt;/li&gt;
&lt;li&gt;Scaling numerical features&lt;/li&gt;
&lt;li&gt;Removing irrelevant features&lt;/li&gt;
&lt;li&gt;Splitting data into training and testing sets&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So even though neural networks are powerful, &lt;strong&gt;data preparation still matters.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Garbage in can still mean garbage out.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-neurons&quot;&gt;2. Neurons&lt;/h4&gt;
&lt;p&gt;The basic building block of a neural network is the &lt;strong&gt;artificial neuron&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A neuron takes several inputs, assigns different importance to them using weights, adds a bias, and then passes the result through an activation function.&lt;/p&gt;
&lt;p&gt;Mathematically, we can represent this as:&lt;/p&gt;
&lt;p&gt;\[
z = w_1x_1 + w_2x_2 + ... + w_nx_n + b
\]&lt;/p&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;\(x\) = input&lt;/li&gt;
&lt;li&gt;\(w\) = weight&lt;/li&gt;
&lt;li&gt;\(b\) = bias&lt;/li&gt;
&lt;li&gt;\(z\) = weighted sum&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The neuron then applies an activation function:&lt;/p&gt;
&lt;p&gt;\[
a = f(z)
\]&lt;/p&gt;
&lt;p&gt;The result becomes the neuron's output.&lt;/p&gt;
&lt;p&gt;This might look complicated initially, but the basic idea is straightforward:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The neuron combines information from its inputs and decides how strongly to activate.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-weights&quot;&gt;3. Weights&lt;/h4&gt;
&lt;p&gt;Weights determine how important different inputs are to a neuron.&lt;/p&gt;
&lt;p&gt;Imagine we're predicting whether a student will pass.&lt;/p&gt;
&lt;p&gt;Perhaps hours studied is more predictive than the number of assignments completed.&lt;/p&gt;
&lt;p&gt;The network can learn this by assigning different weights to those features.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Hours studied       → weight = 0.8
Attendance          → weight = 0.5
Previous score      → weight = 0.9
Assignments         → weight = 0.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These numbers are only examples.&lt;/p&gt;
&lt;p&gt;During training, the neural network continuously adjusts its weights to improve its predictions.&lt;/p&gt;
&lt;p&gt;This is one of the key ideas behind learning in neural networks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The model isn't explicitly told what the correct weights are. It learns them from data.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;4-bias&quot;&gt;4. Bias&lt;/h4&gt;
&lt;p&gt;Another component is the &lt;strong&gt;bias&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Bias gives the neuron some flexibility in determining when it should activate.&lt;/p&gt;
&lt;p&gt;Without going too deep into the mathematics, you can think of the bias as an additional parameter that allows the activation function to shift.&lt;/p&gt;
&lt;p&gt;The basic equation becomes:&lt;/p&gt;
&lt;p&gt;\[
z = wx + b
\]&lt;/p&gt;
&lt;p&gt;The weights control the influence of the inputs, while the bias helps adjust the overall output.&lt;/p&gt;
&lt;p&gt;Both weights and biases are learned during training.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;5-activation-functions&quot;&gt;5. Activation Functions&lt;/h4&gt;
&lt;p&gt;This was one of the concepts that really helped me understand why neural networks can learn complex patterns.&lt;/p&gt;
&lt;p&gt;After calculating the weighted sum, a neuron applies an &lt;strong&gt;activation function&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;Because without activation functions, stacking multiple layers of neurons would still result in a fundamentally linear transformation.&lt;/p&gt;
&lt;p&gt;Activation functions introduce &lt;strong&gt;non-linearity&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;And real-world data is rarely perfectly linear.&lt;/p&gt;
&lt;p&gt;Some commonly used activation functions include:&lt;/p&gt;
&lt;h5 id=&quot;sigmoid&quot;&gt;Sigmoid&lt;/h5&gt;
&lt;p&gt;The sigmoid function produces values between 0 and 1.&lt;/p&gt;
&lt;p&gt;\[
\sigma(x) = \frac{1}{1 + e^{-x}}
\]&lt;/p&gt;
&lt;p&gt;This makes it useful in certain binary classification problems, particularly as an output activation.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0.91 → high probability of class 1
0.12 → low probability of class 1
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h5 id=&quot;relu&quot;&gt;ReLU&lt;/h5&gt;
&lt;p&gt;ReLU stands for &lt;strong&gt;Rectified Linear Unit&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It is defined as:&lt;/p&gt;
&lt;p&gt;\[
ReLU(x) = \max(0,x)
\]&lt;/p&gt;
&lt;p&gt;So:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;x = -3 → 0
x =  2 → 2
x =  7 → 7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ReLU became particularly important in deep learning because it is simple and computationally efficient and helps neural networks model nonlinear relationships.&lt;/p&gt;
&lt;hr&gt;
&lt;h5 id=&quot;softmax&quot;&gt;Softmax&lt;/h5&gt;
&lt;p&gt;Softmax is commonly used in the output layer for multi-class classification.&lt;/p&gt;
&lt;p&gt;Suppose we're classifying an image as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Cat
Dog
Bird
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Softmax can convert the model's outputs into probabilities that sum to 1.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Cat  → 0.10
Dog  → 0.82
Bird → 0.08
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The model would predict &lt;strong&gt;Dog&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;6-hidden-layers&quot;&gt;6. Hidden Layers&lt;/h4&gt;
&lt;p&gt;Now we get to one of the most important parts of a neural network.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;hidden layers&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;These layers sit between the input and output layers.&lt;/p&gt;
&lt;p&gt;A network with only a few layers might look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Input → Hidden → Output
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A deeper network could look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Input
  ↓
Hidden Layer 1
  ↓
Hidden Layer 2
  ↓
Hidden Layer 3
  ↓
Hidden Layer 4
  ↓
Output
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each layer can learn different representations of the data.&lt;/p&gt;
&lt;p&gt;This is where the term &lt;strong&gt;deep learning&lt;/strong&gt; comes from.&lt;/p&gt;
&lt;p&gt;A neural network with many hidden layers is generally described as a deep neural network.&lt;/p&gt;
&lt;p&gt;The interesting part is that the network can learn increasingly complex representations as information moves through the layers.&lt;/p&gt;
&lt;p&gt;For example, in an image recognition task:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Pixels
  ↓
Edges
  ↓
Shapes
  ↓
Patterns
  ↓
Objects
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The network isn't necessarily programmed with these exact rules.&lt;/p&gt;
&lt;p&gt;It learns useful representations from the training data.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;7-forward-propagation&quot;&gt;7. Forward Propagation&lt;/h4&gt;
&lt;p&gt;So how does a neural network actually make a prediction?&lt;/p&gt;
&lt;p&gt;This happens through &lt;strong&gt;forward propagation&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The input data moves through the network from the input layer toward the output layer.&lt;/p&gt;
&lt;p&gt;At each neuron:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Inputs are multiplied by weights.&lt;/li&gt;
&lt;li&gt;The results are added together.&lt;/li&gt;
&lt;li&gt;A bias is added.&lt;/li&gt;
&lt;li&gt;An activation function is applied.&lt;/li&gt;
&lt;li&gt;The result is passed to the next layer.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Eventually, the network produces a prediction.&lt;/p&gt;
&lt;p&gt;Conceptually:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Input
  ↓
Weighted Sum
  ↓
Activation
  ↓
Next Layer
  ↓
Weighted Sum
  ↓
Activation
  ↓
Prediction
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This happens extremely quickly when implemented computationally.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;8-loss-functions&quot;&gt;8. Loss Functions&lt;/h4&gt;
&lt;p&gt;The network makes a prediction.&lt;/p&gt;
&lt;p&gt;But how does it know whether the prediction is good or bad?&lt;/p&gt;
&lt;p&gt;This is where the &lt;strong&gt;loss function&lt;/strong&gt; comes in.&lt;/p&gt;
&lt;p&gt;A loss function measures the difference between the model's prediction and the actual target.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Actual value      = 1
Predicted value   = 0.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is a significant difference.&lt;/p&gt;
&lt;p&gt;The loss function quantifies that error.&lt;/p&gt;
&lt;p&gt;Different problems use different loss functions.&lt;/p&gt;
&lt;p&gt;Some common examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mean Squared Error (MSE)&lt;/li&gt;
&lt;li&gt;Binary Cross-Entropy&lt;/li&gt;
&lt;li&gt;Categorical Cross-Entropy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The general goal during training is simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Minimize the loss.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h4 id=&quot;9-backpropagation&quot;&gt;9. Backpropagation&lt;/h4&gt;
&lt;p&gt;This is probably one of the most important concepts in neural networks.&lt;/p&gt;
&lt;p&gt;After making a prediction and calculating the loss, the network needs to figure out:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Which weights contributed to the error, and how should they change?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is where &lt;strong&gt;backpropagation&lt;/strong&gt; comes in.&lt;/p&gt;
&lt;p&gt;The error is propagated backward through the network.&lt;/p&gt;
&lt;p&gt;Using calculus—particularly the chain rule—the network calculates gradients that indicate how changes in the parameters would affect the loss.&lt;/p&gt;
&lt;p&gt;Conceptually:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Prediction
    ↓
Calculate Loss
    ↓
Propagate Error Backward
    ↓
Calculate Gradients
    ↓
Update Weights
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the process repeats.&lt;/p&gt;
&lt;p&gt;Again.&lt;/p&gt;
&lt;p&gt;And again.&lt;/p&gt;
&lt;p&gt;And again.&lt;/p&gt;
&lt;p&gt;This is how the neural network gradually improves.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;10-optimizers&quot;&gt;10. Optimizers&lt;/h4&gt;
&lt;p&gt;Backpropagation tells us how the parameters should change.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;optimizer&lt;/strong&gt; determines how those changes are actually made.&lt;/p&gt;
&lt;p&gt;One of the most well-known optimization algorithms is &lt;strong&gt;Gradient Descent&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The basic idea is to move the model's parameters in a direction that reduces the loss.&lt;/p&gt;
&lt;p&gt;A simplified update rule looks like:&lt;/p&gt;
&lt;p&gt;\[
w_{new} = w_{old} - \eta \frac{\partial L}{\partial w}
\]&lt;/p&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;\(w\) = weight&lt;/li&gt;
&lt;li&gt;\(\eta\) = learning rate&lt;/li&gt;
&lt;li&gt;\(L\) = loss&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The learning rate controls how large each update should be.&lt;/p&gt;
&lt;p&gt;If the learning rate is too large, the model may overshoot useful solutions.&lt;/p&gt;
&lt;p&gt;If it is too small, training may take a very long time.&lt;/p&gt;
&lt;p&gt;Other optimizers include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adam&lt;/li&gt;
&lt;li&gt;RMSprop&lt;/li&gt;
&lt;li&gt;Adagrad&lt;/li&gt;
&lt;li&gt;SGD with momentum&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Adam is particularly common in practical deep learning applications.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;11-epochs-and-batches&quot;&gt;11. Epochs and Batches&lt;/h4&gt;
&lt;p&gt;Neural networks don't normally learn from the entire dataset in a single step.&lt;/p&gt;
&lt;p&gt;Instead, training data is often divided into smaller groups called &lt;strong&gt;batches&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example, if we have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;10,000 training samples
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;we might use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Batch size = 32
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The network processes 32 samples at a time.&lt;/p&gt;
&lt;p&gt;Once it has processed the entire training dataset, we have completed one &lt;strong&gt;epoch&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;So:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Dataset
   ↓
Batch 1
Batch 2
Batch 3
...
Batch N
   ↓
One Epoch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The model typically trains for multiple epochs.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;12-learning-rate&quot;&gt;12. Learning Rate&lt;/h4&gt;
&lt;p&gt;The learning rate determines how aggressively the model updates its parameters.&lt;/p&gt;
&lt;p&gt;Think of it as the size of the steps the model takes while trying to minimize its loss.&lt;/p&gt;
&lt;p&gt;A very large learning rate might look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Too large
    ↓
Overshoot
    ↓
Unstable training
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A very small learning rate might look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Too small
    ↓
Tiny updates
    ↓
Very slow learning
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finding an appropriate learning rate can make a significant difference in training performance.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;13-training-validation-and-testing&quot;&gt;13. Training, Validation, and Testing&lt;/h4&gt;
&lt;p&gt;Another important concept is understanding how we evaluate a neural network.&lt;/p&gt;
&lt;p&gt;We generally don't want to train and evaluate the model on exactly the same data.&lt;/p&gt;
&lt;p&gt;Instead, data can be divided into:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Dataset
   │
   ├── Training Data
   │
   ├── Validation Data
   │
   └── Test Data
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;training-data&quot;&gt;Training data&lt;/h5&gt;
&lt;p&gt;Used to learn the model's parameters.&lt;/p&gt;
&lt;h5 id=&quot;validation-data&quot;&gt;Validation data&lt;/h5&gt;
&lt;p&gt;Used to monitor performance and help with choices such as hyperparameters and model architecture.&lt;/p&gt;
&lt;h5 id=&quot;test-data&quot;&gt;Test data&lt;/h5&gt;
&lt;p&gt;Used to evaluate how well the final model performs on unseen data.&lt;/p&gt;
&lt;p&gt;This is important because a model can perform extremely well on its training data but poorly on new data.&lt;/p&gt;
&lt;p&gt;That brings us to one of the biggest challenges in machine learning.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;14-overfitting&quot;&gt;14. Overfitting&lt;/h4&gt;
&lt;p&gt;A neural network can become &lt;strong&gt;too good at memorizing the training data&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This is known as overfitting.&lt;/p&gt;
&lt;p&gt;Imagine studying for an exam by memorizing the answers to last year's exact questions.&lt;/p&gt;
&lt;p&gt;You might perform extremely well if the same questions appear.&lt;/p&gt;
&lt;p&gt;But if the questions change, your performance may drop.&lt;/p&gt;
&lt;p&gt;A similar thing can happen with machine learning models.&lt;/p&gt;
&lt;p&gt;The model learns the training examples extremely well but fails to generalize to unseen data.&lt;/p&gt;
&lt;p&gt;Some techniques used to reduce overfitting include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dropout&lt;/li&gt;
&lt;li&gt;Regularization&lt;/li&gt;
&lt;li&gt;Early stopping&lt;/li&gt;
&lt;li&gt;Data augmentation&lt;/li&gt;
&lt;li&gt;More training data&lt;/li&gt;
&lt;li&gt;Simpler model architectures&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h5 id=&quot;why-are-neural-networks-so-powerful-&quot;&gt;Why Are Neural Networks So Powerful?&lt;/h5&gt;
&lt;p&gt;What makes neural networks particularly interesting is their ability to learn &lt;strong&gt;nonlinear relationships and complex representations&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Traditional programming often looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Rules + Data → Output
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Machine learning changes the approach:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Data + Expected Outputs → Learned Rules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And neural networks take this further by learning multiple levels of representation.&lt;/p&gt;
&lt;p&gt;This is why they have become so useful in areas such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Computer vision&lt;/li&gt;
&lt;li&gt;Natural language processing&lt;/li&gt;
&lt;li&gt;Speech recognition&lt;/li&gt;
&lt;li&gt;Recommendation systems&lt;/li&gt;
&lt;li&gt;Time-series forecasting&lt;/li&gt;
&lt;li&gt;Fraud detection&lt;/li&gt;
&lt;li&gt;Medical imaging&lt;/li&gt;
&lt;li&gt;Generative AI&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h5 id=&quot;from-neural-networks-to-deep-learning&quot;&gt;From Neural Networks to Deep Learning&lt;/h5&gt;
&lt;p&gt;A neural network with many layers is generally referred to as a &lt;strong&gt;deep neural network&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Deep learning is essentially about using these deeper architectures to learn increasingly complex representations from data.&lt;/p&gt;
&lt;p&gt;This has led to architectures designed for different kinds of problems.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;h5 id=&quot;convolutional-neural-networks-cnns-&quot;&gt;Convolutional Neural Networks (CNNs)&lt;/h5&gt;
&lt;p&gt;Often associated with image and computer vision tasks.&lt;/p&gt;
&lt;h5 id=&quot;recurrent-neural-networks-rnns-&quot;&gt;Recurrent Neural Networks (RNNs)&lt;/h5&gt;
&lt;p&gt;Designed to work with sequential data, although many modern sequence tasks now use transformer-based architectures instead.&lt;/p&gt;
&lt;h5 id=&quot;transformers&quot;&gt;Transformers&lt;/h5&gt;
&lt;p&gt;Transformers have become extremely important in modern AI, particularly for language and other sequence-based problems.&lt;/p&gt;
&lt;p&gt;They power many of the systems behind today's generative AI applications.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;what-i-take-away-from-learning-neural-networks&quot;&gt;What I Take Away From Learning Neural Networks&lt;/h4&gt;
&lt;p&gt;One thing I appreciate about neural networks is that they changed how I think about machine learning.&lt;/p&gt;
&lt;p&gt;At first, the equations can make the subject feel intimidating.&lt;/p&gt;
&lt;p&gt;You see:&lt;/p&gt;
&lt;p&gt;\[
z = wx+b
\]&lt;/p&gt;
&lt;p&gt;then activation functions, gradients, derivatives, optimization algorithms, layers, epochs, and suddenly it feels like you're drowning in mathematics.&lt;/p&gt;
&lt;p&gt;But when you break the process down, the basic idea becomes much easier to understand.&lt;/p&gt;
&lt;p&gt;A neural network:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;takes data → makes a prediction → measures its error → adjusts itself → tries again.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That cycle is at the heart of learning.&lt;/p&gt;
&lt;p&gt;And I think that's the part that makes neural networks so fascinating.&lt;/p&gt;
&lt;hr&gt;
&lt;h5 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h5&gt;
&lt;p&gt;I'm still learning neural networks, and I don't think understanding them means memorizing every equation or being able to build a state-of-the-art architecture from scratch.&lt;/p&gt;
&lt;p&gt;For me, understanding the fundamentals is more important.&lt;/p&gt;
&lt;p&gt;I want to know:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What is happening to the data?&lt;/li&gt;
&lt;li&gt;Why do we need weights?&lt;/li&gt;
&lt;li&gt;What does a bias actually do?&lt;/li&gt;
&lt;li&gt;Why do we need activation functions?&lt;/li&gt;
&lt;li&gt;How does the model know that it made a mistake?&lt;/li&gt;
&lt;li&gt;How does backpropagation help?&lt;/li&gt;
&lt;li&gt;What does an optimizer actually change?&lt;/li&gt;
&lt;li&gt;How do we know whether the model is learning or simply memorizing?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once these pieces start fitting together, neural networks stop looking like a mysterious black box.&lt;/p&gt;
&lt;p&gt;They become a collection of understandable ideas working together.&lt;/p&gt;
&lt;p&gt;And that's probably the most important lesson I've taken from studying them so far:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Complex models become much less intimidating when you understand the simple ideas underneath them.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I'm still on that learning journey and neural networks are definitely one of the areas I'm excited to explore more deeply.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28356/neural-networks-inspired-by-the-brain-built-for-data</guid>
<pubDate>Wed, 23 Sep 2026 14:01:57 +0000</pubDate>
</item>
<item>
<title>Reverse-Engineering Messy Databases: My End-to-End Data Audits Across 17,000+ Relational Schema Logs</title>
<link>https://coderlegion.com/28337/reverse-engineering-messy-databases-end-end-data-audits-across-000-relational-schema-logs</link>
<description>&lt;h3 id=&quot;from-raw-logs-to-business-strategy-how-i-taught-myself-data-engineering-amp-analytics&quot;&gt;From Raw Logs to Business Strategy: How I Taught Myself Data Engineering &amp;amp; Analytics&lt;/h3&gt;
&lt;p&gt;I didn’t take the traditional path into data. Over the past 12 months, I bypassed the university lecture halls to teach myself relational database architecture, pipeline optimization, and growth analytics by diving straight into raw datasets.&lt;/p&gt;
&lt;p&gt;I look at data through an engineering and business lens. Instead of building passive charts that just look pretty, I query transaction logs and user event data to find hidden operational friction, map out user behavior patterns, and isolate exactly where systems are dropping value. I build end-to-end data systems: raw SQL schemas, data sanitization scripts, interactive Power BI dashboards, and single-page executive reports.&lt;/p&gt;
&lt;p&gt;Below are the architectural breakdowns, technical challenges, and business impact metrics of the four core data infrastructure projects I have engineered from scratch.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;project-1-stockpulse-multi-regional-supply-chain-audit&quot;&gt;Project 1: StockPulse — Multi-Regional Supply Chain Audit&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Technical Stack:&lt;/strong&gt; MySQL (Mathematical Window Functions), Power BI, Advanced Excel&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Codebase &amp;amp; Dashboard Layout:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/hafsamotiwala/Enterprise-Procurement-Supply-Chain-Resilience-Pipeline&quot;&gt;[LINK]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-operational-bottleneck&quot;&gt;The Operational Bottleneck&lt;/h4&gt;
&lt;p&gt;Horizon Marketplace faced shrinking profit margins from slow-moving warehouse stock, while simultaneous supplier shipping delays caused high-demand items to go out of stock on our mobile application. Traditional procurement tracking models failed because they relied on simple historical averages, which immediately miscalculate when data hits high seasonal sales volatility spikes.&lt;/p&gt;
&lt;h4 id=&quot;the-technical-implementation&quot;&gt;The Technical Implementation&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Audited 17,646 operational and sales ledger transactions&lt;/strong&gt; using MySQL window functions to calculate cumulative revenue contributions per SKU.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coded a predictive restocking matrix script&lt;/strong&gt; utilizing sales volatility standard deviations and rolling manufacturer lead timelines.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Formulated dynamic Safety Stock buffers&lt;/strong&gt; and programmed automated database Reorder Point triggers to flag supplier order alerts before shelves hit zero.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-business-impact-metrics&quot;&gt;The Business Impact Metrics&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ABC inventory segmentation&lt;/strong&gt; proved that exactly 23 Class A items drive 80% of total marketplace revenue, showing that the remaining catalog space was tying up critical capital.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quantified a hidden revenue bleed of $63,897.26&lt;/strong&gt; caused by a single high-demand apparel product sitting entirely out of stock for 42 days due to upstream logistics bottlenecks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transitioned a multi-regional operation&lt;/strong&gt; from manual, time-intensive spreadsheets to an automated, predictive data framework.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;project-2-funnelopt-habitarcade-b2c-telemetry-audit&quot;&gt;Project 2: FunnelOpt — HabitArcade B2C Telemetry Audit&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Technical Stack:&lt;/strong&gt; MySQL (Session Overrides), Funnel Analysis, User Lifecycle Mapping&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Codebase &amp;amp; Database Logs:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/hafsamotiwala/-HabitArcade-Enterprise-B2C-SaaS-Funnel-Telemetry-Analysis&quot;&gt;[LINK]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-operational-bottleneck-1&quot;&gt;The Operational Bottleneck&lt;/h4&gt;
&lt;p&gt;A B2C application tracking engine suffered from severe data execution drops where product behavioral logs completely failed to map user activation patterns. The relational schema dropped child telemetry records because they were importing prior to the absolute database finalization of the parent account index logs.&lt;/p&gt;
&lt;h4 id=&quot;the-technical-implementation-1&quot;&gt;The Technical Implementation&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Audited full-funnel growth logs&lt;/strong&gt; across 6 independent acquisition pipelines to analyze customer conversion latency and drop-offs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structured specific MySQL session overrides&lt;/strong&gt; to temporarily bypass structural foreign key constraints.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forced clean data synchronization&lt;/strong&gt; across child records to completely restore parent-child index synchronization without corrupting historical system logs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-business-impact-metrics-1&quot;&gt;The Business Impact Metrics&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Isolated a systemic “Streak-Breaking Crash Tax”&lt;/strong&gt; where application sync errors wiped 30-day user milestones, triggering immediate 1-star reviews and an active 6% to 7% monthly subscriber bleed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exposed an “Inverted Value Curve”&lt;/strong&gt; revealing that high-value annual checkout tiers underperformed monthly subscriptions due to silent gateway billing processor failures blocking top-priced subscription cash settlement.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;project-3-leakstop-olist-b2b-revenue-leakage-amp-onboarding-audit&quot;&gt;Project 3: LeakStop — Olist B2B Revenue Leakage &amp;amp; Onboarding Audit&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Technical Stack:&lt;/strong&gt; MySQL (Schema Engineering), Power BI, Business Intelligence&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Codebase &amp;amp; Analytical Readme:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/hafsamotiwala/B2B-Saas-Funnel-Optimization-Audit&quot;&gt;[LINK]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-operational-bottleneck-2&quot;&gt;The Operational Bottleneck&lt;/h4&gt;
&lt;p&gt;Cross-vendor data duplication across digital marketplace ledgers was heavily skewing gross reporting metrics. Standard relational database joins double-counted total transactions when a customer order spanned multiple independent business vendors in a single checkout loop.&lt;/p&gt;
&lt;h4 id=&quot;the-technical-implementation-2&quot;&gt;The Technical Implementation&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Engineered robust MySQL ingestion and data sanitization scripts&lt;/strong&gt; to enforce strict primary key integrity down to individual item vectors, removing all reporting bias.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Processed user account lifecycles&lt;/strong&gt; to calculate onboarding completion latency across specific B2B verticals.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Programmed a real-time database filter&lt;/strong&gt; to flag slipping user accounts precisely on Day 16 of transaction inactivity.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-business-impact-metrics-2&quot;&gt;The Business Impact Metrics&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Isolated an annual revenue leakage of R$ 600,600&lt;/strong&gt; (approximately $119,175 USD) stemming from 462 merchant accounts that stalled out completely during the onboarding phase.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exposed an invisible latency anomaly&lt;/strong&gt; where logistically complex business profiles stalled for an average of 112 days in domain routing and tax mapping phases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implemented a Day 16 early-warning database filter&lt;/strong&gt; to flag at-risk accounts before they reached a terminal, zero-recovery retention cliff.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;project-4-cohortpace-saas-behavioral-retention-engine&quot;&gt;Project 4: CohortPace — SaaS Behavioral Retention Engine&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Technical Stack:&lt;/strong&gt; MySQL (Subqueries &amp;amp; Relational Joins), Cohort Modeling, Data Analytics&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Codebase &amp;amp; Technical Report:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/hafsamotiwala/SaaS-User-Behavior-Analysis-Email-Creator-Platforms-&quot;&gt;[LINK]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-operational-bottleneck-3&quot;&gt;The Operational Bottleneck&lt;/h4&gt;
&lt;p&gt;Initial application dashboards focused blindly on feature exploration depth, which completely masked why healthy-looking active user profiles were suddenly canceling subscriptions. Traditional active-user metrics acted as a lagging indicator, hiding friction because churning accounts remain active inside broken workflows right up to their exit timelines.&lt;/p&gt;
&lt;h4 id=&quot;the-technical-implementation-3&quot;&gt;The Technical Implementation&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Modeled simulated user cohort transactions&lt;/strong&gt; across multi-platform SaaS behavioral profiles to analyze the core drivers of long-term Monthly Recurring Revenue (MRR).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Used advanced MySQL subqueries and relational joins&lt;/strong&gt; to reconstruct full lifecycle interaction sequences.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mapped user interaction velocity trends&lt;/strong&gt; against subscription lifecycles to isolate clear behavioral churn signatures.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-business-impact-metrics-3&quot;&gt;The Business Impact Metrics&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Proved through data pacing&lt;/strong&gt; sending signals that churning users maintain consistent interaction velocity close to their exit date, showing churn is driven by late-stage workflow utility limits rather than gradual disengagement.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Demonstrated that long-term user monetization&lt;/strong&gt; is strictly dependent on habit formation within a single core workflow rather than broad, multi-feature platform exploration.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion-turning-code-into-business-strategy&quot;&gt;Conclusion: Turning Code Into Business Strategy&lt;/h3&gt;
&lt;p&gt;I don’t need a playbook to get started, and I don’t need a manager to hand me a static checklist. I find the question, ingest the data, clean the schemas, model the answer, and present the metric that directly protects business margins.&lt;/p&gt;
&lt;p&gt;I am actively available for analytical remote contract roles, standalone data audits, and freelance pipeline optimization. If your startup is scaling fast and dealing with messy relational pipelines, let’s connect and fix your backend infrastructure loops.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt; Interactive Portfolio Hub:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://lucky-bit-036.notion.site/HAFSA-5fd489cedd70459ca0237c36a168f30a&quot;&gt;[LINK]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; Contact:&lt;/strong&gt; motiwala603@gmail.com&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; Connect with me:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/hafsa-motiwala-a9608835b/&quot;&gt;[LinkedIn]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Open to analyst remote roles across product, growth, and business analytics. Based in Pakistan, available worldwide.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28337/reverse-engineering-messy-databases-end-end-data-audits-across-000-relational-schema-logs</guid>
<pubDate>Wed, 23 Sep 2026 11:56:39 +0000</pubDate>
</item>
<item>
<title>Why Perfect Backtests Fail: Walk-Forward Optimization and Deflated Sharpe in Python</title>
<link>https://coderlegion.com/26601/why-perfect-backtests-fail-walk-forward-optimization-and-deflated-sharpe-in-python</link>
<description>&lt;h2 id=&quot;why-perfect-backtests-fail-walk-forward-optimization-and-deflated-sharpe-in-python&quot;&gt;Why Perfect Backtests Fail: Walk-Forward Optimization and Deflated Sharpe in Python&lt;/h2&gt;
&lt;p&gt;Every quantitative trader and algorithmic developer knows this pattern: you design an intraday or swing strategy, optimize parameters in MetaTrader, TradingView, or Python, obtain a near-perfect upward equity curve with a Sharpe Ratio of 2.5... and the moment you deploy live capital, the strategy collapses into a severe drawdown.&lt;/p&gt;
&lt;p&gt;Why does this happen mathematically? The answer lies in &lt;strong&gt;curve-fitting (overfitting)&lt;/strong&gt; and &lt;strong&gt;selection bias from multiple testing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In this engineering guide, we implement two foundational quantitative tools in Python to falsify backtests before risking capital:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Walk-Forward Optimization (WFO)&lt;/strong&gt; and the &lt;strong&gt;Walk-Forward Efficiency (WFE)&lt;/strong&gt; ratio proposed by Robert Pardo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deflated Sharpe Ratio (DSR)&lt;/strong&gt; by Bailey and Marcos López de Prado to adjust performance for the number of tested trials $N$.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Full empirical research and 5-market cross-validation study available at &lt;a rel=&quot;nofollow&quot; href=&quot;https://guetaquant.com/blog/optimizacion-walk-forward-trading/&quot;&gt;GuetaQuant&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-the-mathematics-of-walk-forward-optimization-wfo-&quot;&gt;1. The Mathematics of Walk-Forward Optimization (WFO)&lt;/h3&gt;
&lt;p&gt;Unlike traditional backtesting that optimizes parameters across an entire historical sample, Walk-Forward Analysis splits data into sequential sliding windows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;In-Sample (IS):&lt;/strong&gt; Historical lookback window used to optimize strategy parameters.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Out-of-Sample (OOS):&lt;/strong&gt; Forward blind window where optimized parameters are evaluated without further tuning.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;strong&gt;Walk-Forward Efficiency (WFE)&lt;/strong&gt; ratio measures how much annualized return survives in out-of-sample execution:&lt;/p&gt;
&lt;p&gt;\[\\text{WFE} = \\frac{\\text{Annualized OOS Return}}{\\text{Annualized IS Return}} \\times 100\]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\text{WFE} \ge 50\%$: The algorithmic strategy shows adaptive out-of-sample persistence.&lt;/li&gt;
&lt;li&gt;$\text{WFE} &amp;lt; 50\%$: High probability of memorized curve-fitting.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-python-implementation-walk-forward-efficiency&quot;&gt;2. Python Implementation: Walk-Forward Efficiency&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np
import pandas as pd

def calculate_wfe(in_sample_returns: pd.Series, out_of_sample_returns: pd.Series) -&amp;gt; dict:
    &quot;&quot;&quot;
    Computes Walk-Forward Efficiency (WFE) ratio.
    &quot;&quot;&quot;
    ann_is = in_sample_returns.mean() * 252
    ann_oos = out_of_sample_returns.mean() * 252

    wfe = (ann_oos / ann_is) * 100.0 if ann_is &amp;gt; 0 else 0.0

    return {
        &quot;annualized_in_sample_return_pct&quot;: round(ann_is * 100, 2),
        &quot;annualized_out_of_sample_return_pct&quot;: round(ann_oos * 100, 2),
        &quot;walk_forward_efficiency_pct&quot;: round(wfe, 2),
        &quot;verdict&quot;: &quot;ROBUST_ADAPTIVE&quot; if wfe &amp;gt;= 50.0 else &quot;OVERFITTED_REJECTED&quot;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-deflated-sharpe-ratio-dsr-correcting-for-multiple-testing&quot;&gt;3. Deflated Sharpe Ratio (DSR): Correcting for Multiple Testing&lt;/h3&gt;
&lt;p&gt;If you test 100 moving average combinations and pick the single best result, the observed Sharpe ratio is not a standard normal random variable: it is the &lt;strong&gt;maximum of 100 variables&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;According to Extreme Value Theory, the expected maximum Sharpe under the null hypothesis of zero skill ($H_0$) is:&lt;/p&gt;
&lt;p&gt;\[E[\\max_N \\{SR_n\\}] \\approx \\sqrt{V} \\left[ (1 - \\gamma) \\Phi^{-1}\\left(1 - \\frac{1}{N}\\right) + \\gamma \\Phi^{-1}\\left(1 - \\frac{1}{N \\cdot e}\\right) \\right]\]&lt;/p&gt;
&lt;p&gt;Where $\gamma$ is the Euler-Mascheroni constant ($0.5772...$) and $N$ is the total number of parameter combinations tested.&lt;/p&gt;
&lt;p&gt;You can calculate DSR using the open-source library &lt;code&gt;gueta-risk&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install gueta-risk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And execute in Python:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from gueta_risk import deflated_sharpe_ratio

# Suppose you ran N=100 parameter permutations
# and your best backtest showed an annualized Sharpe of 1.40 across 252 daily bars:
result = deflated_sharpe_ratio(
    observed_sr=1.40,
    num_trials=100,
    sample_length=252,
    skewness=-0.1,
    kurtosis=3.2
)

print(result)
# {
#   observed_sharpe: 1.4,
#   num_trials_tested: 100,
#   expected_max_sharpe_threshold: 2.505,
#   deflated_sharpe_ratio: 0.0035,
#   is_significant_95pct: False,
#   falsification_verdict: FALSIFIED_OVERFITTING
# }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt; A Sharpe of 1.40 appears attractive at first glance, but after 100 trials, the expected false discovery threshold is 2.505. Because DSR is 0.0035 (&amp;lt; 0.95), the strategy is &lt;strong&gt;rejected by statistical falsification&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-open-source-quantitative-ecosystem&quot;&gt;4. Open-Source Quantitative Ecosystem&lt;/h3&gt;
&lt;p&gt;All our empirical research, validation scripts, and risk engines are open-source under AGPLv3:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Full Empirical Research Paper:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://guetaquant.com/blog/optimizacion-walk-forward-trading/&quot;&gt;Walk-Forward Optimization Guide on GuetaQuant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;44 Open Source Quantitative Tools:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/guetaquant-byte/guetaquant-tools&quot;&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CERN Zenodo DOI Scientific Registry:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://doi.org/10.5281/zenodo.22012203&quot;&gt;DOI 10.5281/zenodo.22012203&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interactive MT5 Position Sizer:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://guetaquant.com/herramientas/mt5-position-sizer/&quot;&gt;GuetaQuant Risk Tool&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;About the Author: Mahdi Goodarzi is the founder and quant developer at &lt;a rel=&quot;nofollow&quot; href=&quot;https://guetaquant.com&quot;&gt;Gueta Quant&lt;/a&gt;. Developer profile: &lt;a rel=&quot;nofollow&quot; href=&quot;https://g.dev/mahdigoodarzi&quot;&gt;g.dev/mahdigoodarzi&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26601/why-perfect-backtests-fail-walk-forward-optimization-and-deflated-sharpe-in-python</guid>
<pubDate>Wed, 09 Sep 2026 10:54:50 +0000</pubDate>
</item>
<item>
<title>The Parts Of Scientific Work That Are Really Software Problems</title>
<link>https://coderlegion.com/25879/the-parts-of-scientific-work-that-are-really-software-problems</link>
<description>&lt;p&gt;Most of us here write software for products, but a surprising amount of the same work happens in labs and research groups, written by people who were never taught to write code. If you have ever inherited a research script and wondered why it does what it does, this one is for you.&lt;/p&gt;
&lt;h3 id=&quot;where-research-code-actually-breaks&quot;&gt;Where Research Code Actually Breaks&lt;/h3&gt;
&lt;p&gt;Research code fails differently from product code. It rarely crashes in a way anyone notices. It quietly produces a number, and the number is wrong because a filter was applied twice, or a merge silently dropped half the rows, or a random seed moved between runs.&lt;/p&gt;
&lt;p&gt;The habits that catch this are the ones we already use: keep the raw data immutable, make every transform a named step you can rerun from scratch, and check row counts before and after every join. A pipeline that prints how many records survived each stage catches more errors than any amount of careful reading.&lt;/p&gt;
&lt;h3 id=&quot;statistics-is-a-modeling-problem-not-a-library-problem&quot;&gt;Statistics Is A Modeling Problem, Not A Library Problem&lt;/h3&gt;
&lt;p&gt;Calling a t-test is one line in any language. Knowing that a t-test is the wrong tool for your data is the actual work, and no library will tell you.&lt;/p&gt;
&lt;p&gt;The questions that matter come before the function call: what is the unit of observation, are the observations independent, how many comparisons are you running, and what effect size would actually mean something. Get those wrong and a perfectly correct implementation returns a perfectly meaningless p value.&lt;/p&gt;
&lt;h3 id=&quot;reading-a-paper-like-a-code-review&quot;&gt;Reading A Paper Like A Code Review&lt;/h3&gt;
&lt;p&gt;A paper is a claim plus the evidence for it, and it can be reviewed the same way you review a pull request. Read the methods before the results, because the methods tell you what the results are allowed to mean.&lt;/p&gt;
&lt;p&gt;Then check the sample size, look for the comparison that is missing, and see whether the conclusion in the abstract is the same one the data supports. This is a skill, not a talent, and it improves fast once you start doing it deliberately. We keep the practical side of all of this collected in one place, &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.learnhowtoscience.com/data-and-methods/&quot;&gt;the tools and methods behind real science&lt;/a&gt;, covering Python for scientific work, statistics, scientific computing, algorithms, experiment design, research methods and how to read a paper properly.&lt;/p&gt;
&lt;h3 id=&quot;the-takeaway-1&quot;&gt;The Takeaway&lt;/h3&gt;
&lt;p&gt;Scientific work and software work fail in the same places: unclear inputs, unstated assumptions, and results nobody can reproduce. If you already have the engineering instincts, most of what separates you from doing real analysis is vocabulary, and that part is learnable in an afternoon.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25879/the-parts-of-scientific-work-that-are-really-software-problems</guid>
<pubDate>Sun, 30 Aug 2026 21:45:55 +0000</pubDate>
</item>
<item>
<title>Weather Models Solved Honest Error Bars Before The Rest Of Us Did</title>
<link>https://coderlegion.com/25390/weather-models-solved-honest-error-bars-before-the-rest-of-us-did</link>
<description>&lt;p&gt;Most of us ship a model that returns one number and then bolt a confidence score onto it afterwards, usually because somebody asked. Operational meteorology went the other way decades ago: the distribution is the product, and the single run is just one sample from it. The pattern is worth stealing, so here is how it actually works.&lt;/p&gt;
&lt;h3 id=&quot;the-physics-chain-underneath-every-forecast&quot;&gt;The Physics Chain Underneath Every Forecast&lt;/h3&gt;
&lt;p&gt;Weather is not a bag of correlations, it is a solved-forward physics problem. The Sun heats Earth unevenly, temperature differences become pressure differences, pressure differences become wind, wind moves moisture, and moisture becomes clouds and precipitation. Earth's rotation deflects the moving air through the Coriolis effect, which is why cyclones spiral instead of collapsing straight into the low.&lt;/p&gt;
&lt;p&gt;The energy accounting matters more than it looks. Evaporating water stores a large amount of latent heat in the vapor, and when that vapor condenses inside a cloud the heat dumps back into the surrounding air. That release is the fuel that turns an ordinary updraft into a thunderstorm and a tropical disturbance into a hurricane.&lt;/p&gt;
&lt;h3 id=&quot;how-a-numerical-model-actually-runs&quot;&gt;How A Numerical Model Actually Runs&lt;/h3&gt;
&lt;p&gt;Numerical weather prediction slices the atmosphere into a three dimensional grid and integrates the governing equations forward at every point: conservation of mass, momentum and energy, plus the ideal gas law. ECMWF's global system runs at roughly 9km grid spacing. Regional models at 3km or finer stop parameterizing convection and start resolving individual thunderstorms.&lt;/p&gt;
&lt;p&gt;The inputs come from about 10,000 surface stations, roughly 900 radiosonde launches every 12 hours, plus radar, aircraft, buoys and satellites. Geostationary satellites give you the same hemisphere every few minutes, polar orbiters give you higher resolution on a slower revisit.&lt;/p&gt;
&lt;h3 id=&quot;why-the-ensemble-is-the-real-output&quot;&gt;Why The Ensemble Is The Real Output&lt;/h3&gt;
&lt;p&gt;Every one of those observations carries error, and the system is chaotic, so two nearly identical initial states diverge. Lorenz found this inside a weather model in the first place, which means the field inherited the problem and the workaround at the same time.&lt;/p&gt;
&lt;p&gt;The workaround is ensemble forecasting: run the same model many times from slightly perturbed initial conditions and read the spread across members. Tight clustering means high confidence. Wide scatter means several outcomes are genuinely open. A &quot;40 percent chance of rain&quot; is that scatter converted into a number, not a forecaster hedging. This is what pushed medium range forecasts, the 3 to 10 day window, from unusable to genuinely decision grade.&lt;/p&gt;
&lt;h3 id=&quot;what-to-take-back-to-your-own-stack&quot;&gt;What To Take Back To Your Own Stack&lt;/h3&gt;
&lt;p&gt;If your model is deterministic and your users need to act on it, you are shipping the least informative part of what you computed. Perturb the inputs at the level of their real measurement error, run the thing N times, and report the spread. It costs N times the compute and it is usually worth it, because the honest answer to &quot;how sure are you&quot; is a distribution rather than a trained confidence head.&lt;/p&gt;
&lt;p&gt;The full walkthrough of the physics, from dew point and cloud formation through fronts, supercells and hurricanes to how the forecast pipeline is assembled, is in our &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.learnhowtoscience.com/weather-science-explained/&quot;&gt;complete guide to how weather works&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Curious whether anyone here has actually shipped an ensemble instead of a point estimate, and what the compute bill looked like.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25390/weather-models-solved-honest-error-bars-before-the-rest-of-us-did</guid>
<pubDate>Tue, 25 Aug 2026 22:03:01 +0000</pubDate>
</item>
<item>
<title>A SQL interview is a translation test, not a syntax test</title>
<link>https://coderlegion.com/25140/a-sql-interview-is-a-translation-test-not-a-syntax-test</link>
<description>&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://four-leaf.ai/blog/sql-interview-guide&quot;&gt;Four-Leaf's SQL Interview Guide&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key takeaways&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A SQL interview is a translation test. The syntax is the easy half, and most candidates who fail were fluent in SQL. What sinks them is starting to type before the question is pinned down, or writing a correct query that answers something nobody asked. The round scores three things: whether you clarify before you write, whether your query is correct on the edge cases, and whether you can explain your reasoning while your hands are moving. It also looks meaningfully different depending on whether you're interviewing as a data analyst, a data scientist, or an engineer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What does a SQL interview test?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The round tests whether you can turn an underspecified business question into a query that survives contact with real data. Interviewers rarely hand you a clean specification, because the job never does either.&lt;/p&gt;
&lt;p&gt;SQL is worth preparing for on volume alone. The Stack Overflow 2025 Developer Survey found 58.6% of developers had used SQL in the past year, making it the third most-used language behind JavaScript at 66% and HTML/CSS at 61.9%, and ahead of Python at 57.9%. It shows up in loops for roles that aren't nominally data roles at all, which is part of why candidates underprepare for it.&lt;/p&gt;
&lt;p&gt;The tell that separates a strong candidate is what happens in the first sixty seconds. Weak candidates read the prompt and start typing. Strong candidates restate the question, ask what counts as an active user or a completed order, confirm whether the answer should include rows with nulls, and only then write. That opening exchange is often worth more to the interviewer than the query itself, because it's the part of the job that can't be looked up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What kinds of SQL questions come up?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Three shapes cover most of what gets asked, and they escalate in a predictable order.&lt;/p&gt;
&lt;p&gt;The first is a join and aggregation question. Given two or three tables, produce a count or a sum grouped by something. These look trivial and catch people on join type, because an inner join silently drops the users who never ordered, and the question usually wanted them counted as zero.&lt;/p&gt;
&lt;p&gt;The second is a window function question. Rank purchases per customer, find each user's second transaction, compute a running total, or calculate a month-over-month change. This is where a lot of loops separate candidates, because window functions are the boundary between people who write SQL occasionally and people who use it as a primary tool.&lt;/p&gt;
&lt;p&gt;The third is an open business question against a schema you've just been shown. &quot;Tell me whether retention improved after the March release.&quot; There's no single correct query. The interviewer is watching how you decompose the question, what you decide retention means, and whether you notice that the March cohort has less time to churn than the February one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How is a SQL interview scored?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Three dimensions, and they map to those question shapes. It helps to run each one against the questions the round keeps returning to: give me a count by group, rank something per user, and answer this vague business question.&lt;/p&gt;
&lt;p&gt;Do you clarify before you write? A passing answer asks one question about the schema. A strong answer names the ambiguity that would change the query and resolves it. &quot;Does an active user mean any event in the window, or a purchase?&quot; is the difference between two very different numbers, and interviewers plant that ambiguity on purpose.&lt;/p&gt;
&lt;p&gt;Is your query correct on the edges? A passing answer runs. A strong answer accounts for nulls, duplicates, ties in a ranking, and rows on the boundary of a date range. Say the edge case out loud when you handle it, because a silent LEFT JOIN looks identical to a lucky one.&lt;/p&gt;
&lt;p&gt;Can you narrate while you write? A passing answer goes quiet and produces a query. A strong answer talks through the plan first, writes, then checks the result against a rough expectation out loud. Interviewers score reasoning they can hear, and a correct query delivered in silence gets a weaker write-up than a slightly imperfect one that was explained.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How does a SQL interview differ for a data analyst versus a data scientist?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Same language, meaningfully different round, and mixing them up is the most common preparation mistake in the function.&lt;/p&gt;
&lt;p&gt;Data analyst. The emphasis is breadth and business translation. Expect more questions, less depth per question, and heavy weighting on whether your numbers would hold up in front of a stakeholder. Definitional precision matters more than query elegance. You're likely to be asked what a metric should mean before you're asked to compute it, and an answer that flags a misleading denominator scores higher than one that optimizes a join.&lt;/p&gt;
&lt;p&gt;Data scientist. The emphasis shifts toward experiment and cohort logic. Expect fewer questions with more depth, and expect at least one that touches an A/B test readout, a cohort definition, or a sampling problem hiding inside the SQL. A question about whether a March cohort had less time to churn than a February one is a data scientist question. Correctness under a statistical framing is what's being scored, not query volume.&lt;/p&gt;
&lt;p&gt;Analytics engineer. The round moves toward modeling. Expect questions about how you'd structure the model rather than write a one-off answer, including idempotency, incremental logic, and what happens when the query is rerun.&lt;/p&gt;
&lt;p&gt;Software engineer. SQL usually appears inside a broader technical round rather than on its own, and the framing is performance and correctness at scale. Expect indexing, query plans, and the consequences of a full table scan rather than business definitions.&lt;/p&gt;
&lt;p&gt;Product management. Increasingly common and usually light. The bar is whether you can pull your own numbers without asking an analyst, so expect one aggregation question and no window functions.&lt;/p&gt;
&lt;p&gt;Four-Leaf covers the wider data loop in how to prepare for a data science interview.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is overrated&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Memorizing exotic syntax. Recursive CTEs and pivot tricks appear in practice sets far more often than in interviews, and time spent there is time not spent on window functions, which appear constantly.&lt;/p&gt;
&lt;p&gt;Speed is the other overrated thing. Candidates rush because they assume the clock is the test, then miss a join type. The interviewer is almost always more interested in whether you caught the null case than in whether you finished ninety seconds early.&lt;/p&gt;
&lt;p&gt;Using an AI assistant to get through this round is a losing trade. CodeSignal reported in February 2026 that cheating and fraud attempts on proctored assessments more than doubled, from 16% in 2024 to 35% in 2025. Interviewers noticed. A 2025 interviewing.io survey of 67 of them, 52 at FAANG companies, found 81% suspected candidates of using AI and about a third had caught someone at it. The visible response has been more live, narrated rounds, which is precisely the format where a candidate who can't explain their own query falls apart.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A five-step playbook for a SQL round&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Drill window functions until they're automatic. ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, and a running sum. These carry more interview weight per hour of study than anything else in SQL.&lt;/p&gt;
&lt;p&gt;Practice restating the question out loud before writing. Give yourself a rule that you don't type until you've named one ambiguity.&lt;/p&gt;
&lt;p&gt;Build a null and duplicate checklist. Before you call a query done, ask what happens to rows with nulls, rows that appear twice, and rows exactly on the boundary date.&lt;/p&gt;
&lt;p&gt;Do at least three problems on a whiteboard or plain text editor. Autocomplete hides gaps that a bare editor exposes, and many live rounds use a bare editor.&lt;/p&gt;
&lt;p&gt;Narrate a solved problem end to end. Explaining a query you already understand is a separate skill from writing it, and it's the one being scored. A voice mock interview is a reasonable way to rehearse the narration, since the failure mode is going quiet under pressure rather than not knowing the syntax.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where this is heading&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SQL rounds are getting more conversational, and the reason is the same force reshaping every technical loop. As assistants make it trivial to produce a syntactically correct query, interviewers move the bar to the part that's harder to fake, which is deciding what to compute and defending the choice. Four-Leaf's AI-Era Hiring Index, a study of 3,502 open roles at 16 top tech employers, found LLM or foundation-model experience listed in 57% of data and machine learning job descriptions against 21% of engineering ones, so the expectation that data candidates work alongside these tools is already written into the postings.&lt;/p&gt;
&lt;p&gt;That direction rewards the candidate who understood the question. The syntax was never the hard part, and it's about to matter even less. Where this round sits in the wider sequence is covered in how an onsite interview loop actually works. To rehearse it out loud rather than read about it, start with eight coding interview platforms, compared.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25140/a-sql-interview-is-a-translation-test-not-a-syntax-test</guid>
<pubDate>Sat, 22 Aug 2026 14:27:55 +0000</pubDate>
</item>
<item>
<title>Beyond Binary Classification: Engineering an Autonomous Probabilistic Forecasting AI</title>
<link>https://coderlegion.com/24869/beyond-binary-classification-engineering-an-autonomous-probabilistic-forecasting-ai</link>
<description>&lt;p&gt;In the realm of applied data science, building a model that performs well on static datasets is a standard rite of passage. But deploying an AI to predict outcomes in a chaotic, high-variance environment-such as professional sports-is an entirely different engineering challenge.&lt;/p&gt;
&lt;p&gt;When you introduce human variability, real-time tactical shifts, and unpredictable physical elements into your feature space, traditional machine learning models break down. They overfit to historical noise, fail to account for variance, and ultimately output brittle predictions.&lt;/p&gt;
&lt;p&gt;I recently set out to solve this exact data science problem. The objective was to build a system that moves beyond simple binary classification and acts as a true mathematical oracle. The result is &lt;strong&gt;Matchmind Oracle&lt;/strong&gt;, a fully autonomous, ML-driven probabilistic forecasting engine.&lt;/p&gt;
&lt;p&gt;Here is a surgical breakdown of the data science architecture, the feature engineering process, and the mathematical modeling required to build an AI that actually understands chaos.&lt;/p&gt;
&lt;h3 id=&quot;the-failure-of-binary-output-in-chaotic-systems&quot;&gt;The Failure of Binary Output in Chaotic Systems&lt;/h3&gt;
&lt;p&gt;Most entry-level predictive models in sports analytics treat the problem as a standard binary classification task: Team A vs. Team B, Output = 1 or 0. A developer might throw a Random Forest or a basic deep neural network at the problem, optimize for accuracy on a historical dataset, and call it a day.&lt;/p&gt;
&lt;p&gt;This approach is fundamentally flawed. In chaotic environments, predicting a definitive &quot;winner&quot; is statistically dishonest.&lt;/p&gt;
&lt;p&gt;Instead of asking the model to output a binary decision, I engineered Matchmind Oracle to function as a probabilistic forecasting engine. Rather than outputting a single prediction, the model generates a probability density function across a spectrum of possible outcomes. It calculates the exact implied probability of an event, the expected value (EV), and the statistical variance. It doesn’t tell you &lt;em&gt;what&lt;/em&gt; will happen; it tells you the exact mathematical likelihood of &lt;em&gt;every&lt;/em&gt; possibility.&lt;/p&gt;
&lt;h3 id=&quot;high-dimensional-feature-engineering&quot;&gt;High-Dimensional Feature Engineering&lt;/h3&gt;
&lt;p&gt;A machine learning model is effectively blind until you teach it how to see the data. For Matchmind Oracle, simply feeding raw box scores into a neural network would result in garbage outputs. The feature engineering had to be surgical.&lt;/p&gt;
&lt;p&gt;The Oracle ingests massive amounts of structured match data via automated pipelines. However, before that data reaches the inference engine, it passes through a rigorous transformation layer:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Temporal Weighting:&lt;/strong&gt; Recent performance is mathematically more significant than data from three years ago. I implemented time-decay functions on historical data points, ensuring the model heavily weights current form while still maintaining a baseline understanding of long-term franchise or player tendencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contextual Vectorization:&lt;/strong&gt; How do you encode a sudden injury or a weather shift? I built dynamic feature handlers that vectorize contextual data. When an anomaly occurs (e.g., a star player is benched), the model instantly recalculates the matrix, cross-referencing thousands of historical vectors where similar anomalies occurred.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dimensionality Reduction:&lt;/strong&gt; Sports APIs provide an overwhelming amount of raw data. To prevent the model from drowning in high dimensionality, I utilized Principal Component Analysis (PCA) to isolate the statistical features that actually correlate with outcome variance, dropping the &quot;noise&quot; that leads to overfitting.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;the-training-loop-calibrating-confidence&quot;&gt;The Training Loop: Calibrating Confidence&lt;/h3&gt;
&lt;p&gt;Training an AI on sports data requires a delicate balance. If you train it too aggressively, it overfits to the past and fails to generalize to the future.&lt;/p&gt;
&lt;p&gt;To solve this, I built extensive historical evaluation scripts. These are not simple backtests; they are rigorous cross-validation pipelines. The Oracle runs through years of historical data, makes its probabilistic forecasts, and then measures its output against the actual results using logarithmic loss (log-loss) functions.&lt;/p&gt;
&lt;p&gt;Because the system is probabilistic, evaluating its success is highly nuanced. If the Oracle states there is a 30% chance of an underdog winning, and the underdog wins, the model wasn't &quot;wrong&quot;-it correctly identified a high-variance edge. The evaluation scripts continually calibrate the model's confidence intervals. If the model is too confident in highly volatile scenarios, the loss function penalizes it, forcing the algorithm to dynamically adjust its variance parameters.&lt;/p&gt;
&lt;h3 id=&quot;from-jupyter-to-autonomous-inference&quot;&gt;From Jupyter to Autonomous Inference&lt;/h3&gt;
&lt;p&gt;Many data scientists build brilliant models that never leave their Jupyter Notebooks. Taking a complex mathematical framework and turning it into a live, autonomous product requires a bridge between data science and software architecture.&lt;/p&gt;
&lt;p&gt;Matchmind Oracle operates continuously without manual intervention. The inference engine is containerized and runs as a persistent background service on a virtual private server. Whenever new data hits the ingestion pipelines, the model recalculates the probability matrices in real-time.&lt;/p&gt;
&lt;p&gt;But the true innovation lies in the delivery mechanism. I wanted the outputs to be instantly accessible, avoiding the friction of web dashboards. To achieve this, the entire Python-based ML backend is integrated via webhooks into a Telegram bot infrastructure.&lt;/p&gt;
&lt;p&gt;When the model detects a statistically significant edge or a high-probability variance, the automated agent formats the complex data into digestible, mathematically grounded insights and pushes them directly to the user's device.&lt;/p&gt;
&lt;h3 id=&quot;open-source-exploration&quot;&gt;Open Source Exploration&lt;/h3&gt;
&lt;p&gt;Building the Matchmind Oracle has been one of the most rewarding data science projects of my career. It bridges the gap between deep mathematical modeling, rigorous data engineering, and frictionless product delivery. It is a testament to the fact that with clean data and surgical algorithm design, we can quantify chaos.&lt;/p&gt;
&lt;p&gt;I am a strong advocate for the developer community and believe that AI architectures should be explored openly. If you are a machine learning engineer, a data scientist, or a developer fascinated by probabilistic forecasting, I am opening up the system's outputs.&lt;/p&gt;
&lt;p&gt;I have set up a dedicated Telegram channel where the Oracle automatically posts its mathematically driven possibilities and real-time analytical insights for the community to analyze, test, and use logically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Join the Matchmind Oracle Telegram community here to see the ML engine in action:&lt;/strong&gt;&lt;br&gt;
👉 &lt;a rel=&quot;nofollow&quot; href=&quot;https://t.me/+6qJiFtze8sc0ZjM0&quot;&gt;https://t.me/+6qJiFtze8sc0ZjM0&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24869/beyond-binary-classification-engineering-an-autonomous-probabilistic-forecasting-ai</guid>
<pubDate>Tue, 18 Aug 2026 20:34:18 +0000</pubDate>
</item>
<item>
<title>Firecrawl vs Apify vs Quorel</title>
<link>https://coderlegion.com/23443/firecrawl-vs-apify-vs-quorel</link>
<description>&lt;p&gt;Firecrawl and Apify both do the hard part well. They render JavaScript, dodge bot detection, and hand you back clean markdown or structured JSON. If you need a page turned into text, either one will do that job.&lt;/p&gt;
&lt;p&gt;But most of the time the point isn't a single page. It's a page you need to check again tomorrow, and the day after, and the day after that: a price, a listing, a status, a set of rows that might quietly change. And that's where the workflow, not the extraction, starts costing you.&lt;/p&gt;
&lt;p&gt;Firecrawl bills per credit, and the credit math is not what the pricing page implies. Scrape is 1 credit. Add JSON extraction and Enhanced Mode — both of which you probably want for structured data — and it's 9 credits per page. A $16/mo Hobby plan advertised at 3,000 credits works out closer to 300–500 pages once you're actually extracting fields, not just fetching markdown. Credits also don't roll over, so a quiet month doesn't bank anything for a busy one.&lt;/p&gt;
&lt;p&gt;Apify flips the complaint around. Pricing is compute-unit based (RAM × time) plus separate line items for proxy and storage. It's flexible and it scales, but you can't look at a number on the pricing page and know what a job will cost until you've run it. For teams that want a predictable bill, that unpredictability is the whole problem.&lt;/p&gt;
&lt;p&gt;Neither tool keeps history by default. You get the current state of the page. If you want yesterday's version to compare against today's, that's on you to build — a database, a diffing script, a place to store snapshots, a way to roll back if a refresh comes back wrong.&lt;/p&gt;
&lt;p&gt;Quorel starts from a different question: not &quot;how do I fetch this page,&quot; but &quot;how do I keep a page as structured, versioned data over time.&quot; You describe the fields you want in plain English. Every refresh (nightly by default, or on demand via a ping URL, webhook, or your own cron) is saved as its own immutable version. Nothing is overwritten. You can diff any two versions to see exactly what changed, and roll back in one click if a refresh comes back bad.&lt;/p&gt;
&lt;p&gt;Pricing is flat, not metered. Free tier is one dataset, 20 URLs, full version history included. Pro is $19/mo for 5 datasets and 100 URLs, with on-demand refresh and webhooks. No credit multipliers, no compute-unit math, no surprise bill.&lt;/p&gt;
&lt;p&gt;This isn't a claim that Quorel replaces Firecrawl or Apify at everything they do. If you need to fetch an arbitrary one-off page as clean markdown for an LLM right now, Firecrawl is built for exactly that. If you need thousands of site-specific scrapers off a marketplace, Apify's Actor ecosystem has years of head start. Quorel is built for the narrower, recurring job: turning a page you check regularly into a structured, versioned API you can trust and query later.&lt;/p&gt;
&lt;p&gt;Every dataset ships with an MCP server on every plan, free included, so an agent can query it directly — or pull the raw rows, fix nulls, and publish a new version itself.&lt;/p&gt;
&lt;p&gt;If you've been burned by a scraper that silently broke, or wished you'd kept last week's data to compare against, that's the specific gap Quorel is built to close.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;I'm building &lt;a rel=&quot;nofollow&quot; href=&quot;https://quorel.vercel.app&quot;&gt;Quorel&lt;/a&gt; — happy to answer questions on versioning, pricing, or how the MCP integration works.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23443/firecrawl-vs-apify-vs-quorel</guid>
<pubDate>Sat, 25 Jul 2026 21:23:40 +0000</pubDate>
</item>
<item>
<title>How I built a computational AMP screening pipeline: from 24,000 sequences to 47 drug candidates</title>
<link>https://coderlegion.com/21916/how-built-computational-amp-screening-pipeline-from-24-000-sequences-to-drug-candidates</link>
<description>&lt;p&gt;Antimicrobial resistance is projected to kill 10 million people annually by 2050 - more than cancer. Antimicrobial peptides (AMPs) are one of the most promising therapeutic classes to fight it. They're cationic, membrane-disrupting, and bacteria struggle to develop resistance against them.&lt;/p&gt;
&lt;p&gt;The problem: there are thousands of candidate sequences in databases. How do you identify the ones worth synthesising?&lt;/p&gt;
&lt;p&gt;In this post I'll walk through a full computational screening pipeline I built from raw database download through machine learning activity prediction to ColabFold structure prediction - ending with 47 high-confidence drug candidates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The full code is on GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Farhan89082/amp-colabfold&quot;&gt;amp-colabfold&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-pipeline-at-a-glance&quot;&gt;The pipeline at a glance&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/Farhan89082/amp-colabfold/main/results/figures/pipeline.png&quot; alt=&quot;Pipeline&quot;&gt;&lt;/p&gt;
&lt;p&gt;Five stages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Data curation&lt;/strong&gt; - 24,076 sequences from DRAMP 4.0 down to 12,435 after filtering&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feature engineering&lt;/strong&gt; - 16 physicochemical features, 0% missing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Activity modelling&lt;/strong&gt; - gradient boosting classifier, ROC-AUC 0.77&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Candidate selection&lt;/strong&gt; - 131 structured peptides sent to ColabFold&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structure prediction&lt;/strong&gt; - 69 high-confidence structures, 47 very high confidence&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;stage-1-data-curation&quot;&gt;Stage 1: Data curation&lt;/h3&gt;
&lt;p&gt;I pulled sequences from DRAMP 4.0 (the Database of Antimicrobial Peptides) &lt;br&gt;
using their REST API - no manual downloads needed.&lt;/p&gt;
&lt;p&gt;Three filters applied in sequence:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Length: 10-50 aa (therapeutic window for AMPs)
df = df[df[&quot;sequence&quot;].str.len().between(10, 50)]

# Canonical amino acids only - no modified residues
CANONICAL = set(&quot;ACDEFGHIKLMNPQRSTVWY&quot;)
df = df[df[&quot;sequence&quot;].apply(lambda s: set(s).issubset(CANONICAL))]

# 90% identity clustering — Python reimplementation of CD-HIT
# (CD-HIT is Linux-only; I wrote a greedy longest-first algorithm in pure Python)
df_curated = cluster_cdhit_python(df, identity=0.90)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The CD-HIT reimplementation was the most interesting part of this stage. &lt;br&gt;
CD-HIT's greedy algorithm is conceptually simple: sort sequences longest-first, then for each sequence check if it's ≥90% identical to any existing cluster representative. If yes, discard it. If no, it becomes a new representative. I added 3-mer pre-filtering to skip obviously dissimilar pairs, matching CD-HIT's default behaviour.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Result: 24,076 → 12,435 sequences&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;stage-2-feature-engineering&quot;&gt;Stage 2: Feature engineering&lt;/h3&gt;
&lt;p&gt;For each of the 12,435 sequences I computed 16 physicochemical features using &lt;br&gt;
the &lt;code&gt;peptides&lt;/code&gt; Python package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;features = {
    &quot;net_charge_pH7&quot;:           pep.charge(pH=7.0, pKscale=&quot;Lehninger&quot;),
    &quot;hydrophobicity_eisenberg&quot;:  pep.hydrophobicity(scale=&quot;Eisenberg&quot;),
    &quot;hydrophobic_moment&quot;:        pep.hydrophobic_moment(window=11, angle=100),
    &quot;boman_index&quot;:               pep.boman(),
    &quot;aliphatic_index&quot;:           pep.aliphatic_index(),
    &quot;instability_index&quot;:         pep.instability_index(),
    # + Chou-Fasman helix/sheet/turn propensities computed manually
    # + aromaticity, fraction_positive, fraction_negative, length, pI, MW
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One methodological note: &lt;code&gt;aromaticity()&lt;/code&gt; was removed from newer versions of &lt;br&gt;
&lt;code&gt;peptides&lt;/code&gt;, so I computed it directly from residue frequencies &lt;br&gt;
&lt;code&gt;(F + Y + W) / length&lt;/code&gt; — same definition as Biopython, just implemented inline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Result: 16 features, 0% missing across all 12,435 sequences&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;stage-3-activity-modelling&quot;&gt;Stage 3: Activity modelling&lt;/h3&gt;
&lt;p&gt;Binary classification: &lt;code&gt;antibacterial&lt;/code&gt; (confirmed MIC data) vs &lt;br&gt;
&lt;code&gt;general/natural&lt;/code&gt; (uncharacterised or broad labels).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;clf = GradientBoostingClassifier(
    n_estimators=300,
    learning_rate=0.05,
    max_depth=3,        # shallow trees → interpretable SHAP values
    subsample=0.8,
    random_state=42,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;ROC-AUC: 0.77&lt;/strong&gt; on a length-decile-stratified holdout set.&lt;/p&gt;
&lt;p&gt;The class imbalance (28.9% positive) is real and intentional — confirmed &lt;br&gt;
antibacterial sequences with MIC data are rarer than general database entries. &lt;/p&gt;
&lt;p&gt;I didn't oversample, because the model's conservative predictions are &lt;br&gt;
appropriate for a screening tool: better to miss some true positives than &lt;br&gt;
flood the downstream ColabFold stage with false ones.&lt;/p&gt;
&lt;h4 id=&quot;what-drives-the-predictions-&quot;&gt;What drives the predictions?&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/Farhan89082/amp-colabfold/main/results/figures/shap_beeswarm.png&quot; alt=&quot;SHAP beeswarm&quot;&gt;&lt;/p&gt;
&lt;p&gt;The SHAP beeswarm tells a clean biological story:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Length&lt;/strong&gt; dominates - longer peptides push positive. This reflects the &lt;br&gt;
antibacterial subset containing more full-length defensins and cathelicidins.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fraction_helix&lt;/strong&gt; - high helix propensity → predicted antibacterial. &lt;br&gt;
Consistent with &lt;strong&gt;alpha&lt;/strong&gt;-helical membrane disruption.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fraction_positive&lt;/strong&gt; - cationic residues push positive. Textbook &lt;br&gt;
electrostatic attraction to anionic bacterial membranes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fraction_negative&lt;/strong&gt; - anionic residues suppress prediction. Expected.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is not a black box. Every prediction is traceable to a physical property with a known biological mechanism. That's the point of using shallow trees with SHAP rather than a deep model.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;stage-4-candidate-selection&quot;&gt;Stage 4: Candidate selection&lt;/h3&gt;
&lt;p&gt;From the classifier output I selected peptides with &lt;code&gt;ab_proba ≥ 0.938&lt;/code&gt; and &lt;br&gt;
then applied a low-complexity filter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def is_low_complexity(seq: str, threshold: float = 0.5) -&amp;gt; bool:
    # Flag homopolymers (KKKKKK, RRRRRR) and tandem repeats
    max_freq = max(seq.count(aa) for aa in set(seq)) / len(seq)
    if max_freq &amp;gt; threshold:
        return True
    for i in range(len(seq) - 2):
        kmer = seq[i:i+3]
        if seq.count(kmer) * 3 / len(seq) &amp;gt; 0.6:
            return True
    return False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This step is important. Homopolymeric sequences (poly-K, poly-R) score high &lt;br&gt;
on &lt;code&gt;fraction_positive&lt;/code&gt; and fool the classifier - they're real AMPs but &lt;br&gt;
structurally trivial and not useful for ColabFold. The filter removed 44 &lt;br&gt;
low-complexity sequences.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Result: 131 structured candidates (15–44 aa) sent to ColabFold&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;stage-5-structure-prediction-with-colabfold&quot;&gt;Stage 5: Structure prediction with ColabFold&lt;/h3&gt;
&lt;p&gt;I ran ColabFold 1.6.1 on Google Colab (free T4 GPU) in single-sequence mode appropriate for short peptides where MSA depth is limited anyway.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;colabfold_batch \
  colabfold_input.fasta \
  colabfold_output/ \
  --model-type alphafold2_ptm \
  --num-recycle 3 \
  --num-models 1 \
  --msa-mode single_sequence
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For 131 peptides of 15-44 aa, the full run took ~15 minutes on a T4.&lt;/p&gt;
&lt;h4 id=&quot;parsing-results-locally&quot;&gt;Parsing results locally&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def parse_scores_json(json_path):
    with open(json_path) as f:
        data = json.load(f)
    return {
        &quot;mean_plddt&quot;: float(np.mean(data[&quot;plddt&quot;])),
        &quot;ptm&quot;:        float(data[&quot;ptm&quot;]),
        &quot;plddt_per_residue&quot;: data[&quot;plddt&quot;],
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;plddt-distribution&quot;&gt;pLDDT distribution&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/Farhan89082/amp-colabfold/main/results/figures/plddt_distribution.png&quot; alt=&quot;pLDDT distribution&quot;&gt;&lt;/p&gt;
&lt;p&gt;69 of 131 candidates (52.7%) have pLDDT ≥ 70, indicating meaningful structural confidence. For short peptides - which are inherently more disordered than globular proteins - this is a strong result.&lt;/p&gt;
&lt;h4 id=&quot;structural-confidence-vs-activity-prediction&quot;&gt;Structural confidence vs activity prediction&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/Farhan89082/amp-colabfold/main/results/figures/plddt_vs_abproba.png&quot; alt=&quot;pLDDT vs ab_proba&quot;&gt;&lt;/p&gt;
&lt;p&gt;The scatter shows no strong correlation between classifier confidence and &lt;br&gt;
structural confidence - which is biologically expected. A peptide can be &lt;br&gt;
highly predicted antibacterial (high &lt;code&gt;ab_proba&lt;/code&gt;) but disordered in solution &lt;br&gt;
(low pLDDT), because many AMPs adopt structure only upon membrane contact.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-results&quot;&gt;Final results&lt;/h3&gt;
&lt;p&gt;Applying both filters - &lt;code&gt;pLDDT ≥ 90&lt;/code&gt; AND &lt;code&gt;ab_proba ≥ 0.95&lt;/code&gt; - gives &lt;br&gt;
&lt;strong&gt;47 very high-confidence candidates&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;AMP ID &lt;/th&gt;&lt;th&gt;Length &lt;/th&gt;&lt;th&gt;pLDDT &lt;/th&gt;&lt;th&gt;pTM &lt;/th&gt;&lt;th&gt;ab_proba&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;AMP_02388 &lt;/td&gt;&lt;td&gt;30 aa &lt;/td&gt;&lt;td&gt;97.3 &lt;/td&gt;&lt;td&gt;0.54 &lt;/td&gt;&lt;td&gt;0.952&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;AMP_01454 &lt;/td&gt;&lt;td&gt;36 aa &lt;/td&gt;&lt;td&gt;97.2 &lt;/td&gt;&lt;td&gt;0.60 &lt;/td&gt;&lt;td&gt;0.985&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;AMP_02689 &lt;/td&gt;&lt;td&gt;28 aa &lt;/td&gt;&lt;td&gt;97.2 &lt;/td&gt;&lt;td&gt;0.50 &lt;/td&gt;&lt;td&gt;0.954&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;AMP_01282 &lt;/td&gt;&lt;td&gt;37 aa &lt;/td&gt;&lt;td&gt;97.1 &lt;/td&gt;&lt;td&gt;0.61 &lt;/td&gt;&lt;td&gt;0.981&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;AMP_01129 &lt;/td&gt;&lt;td&gt;38 aa &lt;/td&gt;&lt;td&gt;97.0 &lt;/td&gt;&lt;td&gt;0.62 &lt;/td&gt;&lt;td&gt;0.959&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Full list: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Farhan89082/amp-colabfold/blob/main/results/candidate_amps.csv&quot;&gt;candidate_amps.csv&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-d-do-next&quot;&gt;What I'd do next&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hemolysis screening&lt;/strong&gt; - filter candidates through HemoPI2 to remove cytotoxic sequences before any wet-lab work&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESM-2 embeddings&lt;/strong&gt; - train a second classifier on sequence embeddings and compare against the physicochemical feature model&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MD simulation&lt;/strong&gt; - for the top 5 candidates, run short molecular dynamics to assess membrane-binding stability&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Experimental validation&lt;/strong&gt; - MIC assays against &lt;em&gt;E. coli&lt;/em&gt; and &lt;em&gt;S. aureus&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;key-lessons&quot;&gt;Key lessons&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Reproducibility over convenience.&lt;/strong&gt; The CD-HIT reimplementation in pure Python adds ~100 lines but means the entire pipeline runs on any OS without binary dependencies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Class imbalance is a feature, not a bug.&lt;/strong&gt; The 3:1 negative:positive &lt;br&gt;
ratio reflects reality. Don't oversample your way out of it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Interpret your model before trusting it.&lt;/strong&gt; The SHAP beeswarm told me &lt;br&gt;
the model was learning real biology (charge, helix propensity) rather than &lt;br&gt;
spurious correlations. Without that check I wouldn't have been confident &lt;br&gt;
sending its predictions to a GPU.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. pLDDT ≠ activity.&lt;/strong&gt; Low structural confidence doesn't mean an AMP is &lt;br&gt;
inactive - it may just be disordered until it hits a membrane. Keep both &lt;br&gt;
metrics and let them inform each other.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;http://github.com/Farhan89082/amp-colabfold&quot;&gt;github.com/Farhan89082/amp-colabfold&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you found this useful or have questions about any stage, drop a comment below.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21916/how-built-computational-amp-screening-pipeline-from-24-000-sequences-to-drug-candidates</guid>
<pubDate>Sat, 04 Jul 2026 22:33:41 +0000</pubDate>
</item>
<item>
<title>How I Used Python to Analyse 40,000 Human Gut Cells and Uncover What Makes Crohn's Disease Different</title>
<link>https://coderlegion.com/21912/how-used-python-analyse-000-human-gut-cells-and-uncover-what-makes-crohns-disease-different</link>
<description>&lt;p&gt;&lt;em&gt;A step-by-step walkthrough of my multi-sample single-cell RNA sequencing project - written for anyone curious about how computational biology actually works&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;before-we-start---what-is-this-article-actually-about-&quot;&gt;Before we start - what is this article actually about?&lt;/h3&gt;
&lt;p&gt;Imagine being able to take a tiny sample of tissue from a patient's gut, and instead of just knowing &quot;there are cells here&quot;, you could read the activity of every single gene inside every single individual cell - thousands of cells at once.&lt;/p&gt;
&lt;p&gt;That's what single-cell RNA sequencing (scRNA-seq) does. And in this article, I'll walk you through how I used Python to analyse data from 40,000 human gut cells across 18 patients to understand what makes two similar gut diseases - Crohn's disease and ulcerative colitis - biologically different from each other.&lt;/p&gt;
&lt;p&gt;No biology PhD required. I'll explain every term as we go.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-biological-problem---two-diseases-that-look-the-same-but-aren-t&quot;&gt;The biological problem - two diseases that look the same but aren't&lt;/h3&gt;
&lt;p&gt;Crohn's disease (CD) and ulcerative colitis (UC) are both types of Inflammatory Bowel Disease (IBD). Both cause chronic inflammation in the gut, both cause pain and discomfort, and both are lifelong conditions. Doctors have known for decades that they're different diseases, but at a surface level they're easy to confuse.&lt;/p&gt;
&lt;p&gt;The key difference is &lt;strong&gt;where&lt;/strong&gt; and &lt;strong&gt;how&lt;/strong&gt; they inflame:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Crohn's disease&lt;/strong&gt; can affect any part of the digestive tract, tends to cause deep, patchy inflammation, and is driven largely by a type of immune cell called a myeloid cell (think macrophages - the &quot;pac-man&quot; cells of the immune system)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ulcerative colitis&lt;/strong&gt; affects only the colon and rectum, causes continuous surface inflammation, and is driven more by antibody-producing cells called plasma cells&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Understanding these differences at the level of individual cells - not just tissue - is critical for developing better, more targeted treatments. That's where single-cell sequencing comes in.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-data---18-patients-40-000-cells-one-big-challenge&quot;&gt;The data - 18 patients, 40,000 cells, one big challenge&lt;/h3&gt;
&lt;p&gt;I used a publicly available dataset from the CZ CELLxGENE platform containing colonic mucosa (colon lining) biopsies from 18 donors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;6 patients with Crohn's disease&lt;/li&gt;
&lt;li&gt;6 patients with ulcerative colitis&lt;/li&gt;
&lt;li&gt;6 healthy controls&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each donor contributed thousands of cells, giving us 46,700 cells total (40,084 after quality filtering).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But here's the problem:&lt;/strong&gt; when you combine data from 18 different people, collected at different times, processed slightly differently in the lab - the data gets messy. The technical differences between samples (called &lt;strong&gt;batch effects&lt;/strong&gt;) can be so strong that they hide the real biological differences you actually care about.&lt;/p&gt;
&lt;p&gt;Think of it like trying to compare photos taken by 18 different cameras with different colour settings. The same object might look completely different just because of the camera, not because the object actually changed.&lt;/p&gt;
&lt;p&gt;This is where &lt;strong&gt;Harmony&lt;/strong&gt; comes in.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-harmony-and-why-does-it-matter-&quot;&gt;What is Harmony and why does it matter?&lt;/h3&gt;
&lt;p&gt;Harmony is a batch correction algorithm. In plain English: it's a mathematical tool that looks at all 18 samples, figures out which differences between samples are due to technical variation (the &quot;camera settings&quot;), and removes those differences — leaving only the real biological signal.&lt;/p&gt;
&lt;p&gt;Here's what the data looks like &lt;strong&gt;before&lt;/strong&gt; Harmony:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;PCA plot coloured by donor - you can see individual donors clustering separately, meaning donor identity is driving the structure more than the actual biology.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And &lt;strong&gt;after&lt;/strong&gt; Harmony:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;UMAP plot coloured by donor - all 18 donors are now mixed uniformly within each cell type cluster. The batch effects are gone.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But crucially - when we colour the same post-Harmony UMAP by &lt;strong&gt;disease group&lt;/strong&gt; instead of donor:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;UMAP coloured by disease - Crohn's, UC, and normal cells now separate into distinct regions. The biology is preserved, the noise is removed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This before/after comparison is the core technical contribution of this project. Without Harmony, any findings could be explained by &quot;oh, that's just because donor 3 was processed differently.&quot; With Harmony, we can be confident the differences are real.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;setting-up-the-pipeline-in-python&quot;&gt;Setting up the pipeline in Python&lt;/h3&gt;
&lt;p&gt;The full analysis uses &lt;strong&gt;Scanpy&lt;/strong&gt; - the standard Python library for single-cell analysis - along with &lt;strong&gt;harmonypy&lt;/strong&gt; for the integration step.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import scanpy as sc
import harmonypy

# Load the dataset
adata = sc.read_h5ad('data/ibd_dataset.h5ad')
# 46,700 cells × 32,354 genes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The data is stored in an &lt;code&gt;AnnData&lt;/code&gt; object - think of it as a very specialised spreadsheet where rows are cells, columns are genes, and there's extra space to store metadata like disease status, donor ID, and cell type labels.&lt;/p&gt;
&lt;h4 id=&quot;step-1---quality-control&quot;&gt;Step 1 - Quality control&lt;/h4&gt;
&lt;p&gt;Not every cell in a sequencing experiment is a real, healthy cell. Some are damaged, some are empty droplets that got accidentally captured, and some are doublets (two cells mistakenly counted as one). We filter these out using three metrics:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Remove low quality cells
adata = adata[adata.obs['n_genes_by_counts'] &amp;gt; 200]   # too few genes = empty droplet
adata = adata[adata.obs['n_genes_by_counts'] &amp;lt; 6000]  # too many genes = doublet
adata = adata[adata.obs['pct_counts_mt'] &amp;lt; 30]        # high mitochondrial % = dying cell
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why mitochondrial genes?&lt;/strong&gt; When a cell is dying or stressed, the nucleus breaks down and releases its RNA - but mitochondria (the cell's energy factories) have their own separate DNA and RNA that stays intact longer. So a high percentage of mitochondrial gene reads is a reliable sign of a low-quality cell.&lt;/p&gt;
&lt;p&gt;After filtering: 40,084 cells remain.&lt;/p&gt;
&lt;h4 id=&quot;step-2---normalisation&quot;&gt;Step 2 - Normalisation&lt;/h4&gt;
&lt;p&gt;Different cells capture different amounts of RNA simply due to technical variation in the sequencing process. A cell with 10,000 RNA molecules captured will look like it expresses every gene more than a cell with only 2,000 captured — even if their true biology is identical.&lt;/p&gt;
&lt;p&gt;We normalise by scaling every cell to have the same total count (10,000), then apply a log transformation to reduce the influence of very highly expressed genes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-3---finding-highly-variable-genes&quot;&gt;Step 3 - Finding highly variable genes&lt;/h4&gt;
&lt;p&gt;Out of 32,354 genes, most are either not expressed at all or expressed at the same level in every cell (housekeeping genes that keep basic cell functions running). These genes add noise without adding information.&lt;/p&gt;
&lt;p&gt;We select only the &lt;strong&gt;highly variable genes&lt;/strong&gt; - genes whose expression varies meaningfully between cells - for downstream analysis. We found 2,873 of these, using &lt;code&gt;batch_key='donor_id'&lt;/code&gt; to ensure we pick genes that are variable across all donors, not just one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sc.pp.highly_variable_genes(adata, min_mean=0.0125, max_mean=3,
                             min_disp=0.5, batch_key='donor_id')
# Result: 2,873 highly variable genes
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-4---pca-and-harmony-integration&quot;&gt;Step 4 - PCA and Harmony integration&lt;/h4&gt;
&lt;p&gt;PCA (Principal Component Analysis) reduces our 2,873-gene matrix into 50 dimensions that capture the most important variation. Then Harmony corrects for batch effects within this PCA space:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sc.tl.pca(adata, svd_solver='arpack')
sc.external.pp.harmony_integrate(adata, key='donor_id', max_iter_harmony=20)
# Harmony converged in just 6 iterations
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The fact that Harmony converged in only 6 out of 20 possible iterations is a good sign - it means the batch effects were relatively structured and correctable, and the biological signal is strong.&lt;/p&gt;
&lt;h4 id=&quot;step-5---umap-visualisation&quot;&gt;Step 5 - UMAP visualisation&lt;/h4&gt;
&lt;p&gt;UMAP (Uniform Manifold Approximation and Projection) takes the Harmony-corrected embeddings and projects them into 2D for visualisation. Similar cells end up close together, different cells end up far apart:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sc.pp.neighbors(adata, use_rep='X_pca_harmony', n_neighbors=10)
sc.tl.umap(adata)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-we-found---the-biology&quot;&gt;What we found - the biology&lt;/h3&gt;
&lt;h4 id=&quot;finding-1-five-major-cell-populations-in-the-gut&quot;&gt;Finding 1: Five major cell populations in the gut&lt;/h4&gt;
&lt;p&gt;The UMAP revealed five well-separated clusters corresponding to the major cell types in the colon:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Plasma cells&lt;/strong&gt; (15,633 cells) - antibody factories&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Colon epithelial cells&lt;/strong&gt; (12,347 cells) - the cells lining the gut wall&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T cells&lt;/strong&gt; (12,128 cells) - immune soldiers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Myeloid cells&lt;/strong&gt; (3,771 cells) - macrophages and related immune cells&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stromal cells&lt;/strong&gt; (2,821 cells) - structural support cells&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;finding-2-uc-and-cd-have-completely-different-cellular-makeups&quot;&gt;Finding 2: UC and CD have completely different cellular makeups&lt;/h4&gt;
&lt;p&gt;This is where the biology gets interesting. When we look at what proportion of cells belong to each type across disease groups:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Plasma cells:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Normal: ~27%&lt;/li&gt;
&lt;li&gt;Crohn's disease: ~30%&lt;/li&gt;
&lt;li&gt;Ulcerative colitis: &lt;strong&gt;~52%&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;UC has nearly double the plasma cell proportion of healthy tissue. Plasma cells make antibodies - this confirms that UC is primarily driven by antibody-mediated (humoral) immunity, a well-established finding that we independently reproduced from raw data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Myeloid cells:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Normal: ~6%&lt;/li&gt;
&lt;li&gt;Ulcerative colitis: ~8%&lt;/li&gt;
&lt;li&gt;Crohn's disease: &lt;strong&gt;~13%&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CD has more than double the myeloid cell proportion. Myeloid cells include macrophages - the cells responsible for the granulomatous (nodule-forming) inflammation that's the hallmark of Crohn's disease.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Epithelial cells:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Normal: ~29%&lt;/li&gt;
&lt;li&gt;Crohn's disease: ~19%&lt;/li&gt;
&lt;li&gt;Ulcerative colitis: &lt;strong&gt;~3%&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is striking. UC patients have almost no epithelial cells left - the gut lining is severely disrupted. This explains why UC causes such pronounced mucosal damage and bleeding.&lt;/p&gt;
&lt;h4 id=&quot;finding-3-the-s100a8-s100a9-signature---a-clinical-biomarker-reproduced-at-single-cell-resolution&quot;&gt;Finding 3: The S100A8/S100A9 signature - a clinical biomarker reproduced at single-cell resolution&lt;/h4&gt;
&lt;p&gt;To find which specific genes are driving the myeloid difference in Crohn's disease, we ran a differential expression analysis comparing CD myeloid cells against normal myeloid cells using the Wilcoxon rank-sum test.&lt;/p&gt;
&lt;p&gt;The top upregulated genes were:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Gene &lt;/th&gt;&lt;th&gt;What it does&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;S100A8&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Subunit of calprotectin - a protein released by activated immune cells&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;S100A9&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;The other subunit of calprotectin&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;CXCL8&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Also known as IL-8 - a chemical signal that recruits more immune cells to the inflammation site&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;IL1RN&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;An anti-inflammatory signal - the body trying to dampen its own response&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;BCL2A1&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Keeps immune cells alive longer in the inflamed environment&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Here's why S100A8 and S100A9 matter: together they form a protein called &lt;strong&gt;calprotectin&lt;/strong&gt;. When gut inflammation is active, immune cells release calprotectin into the stool. Doctors measure this in a routine test called the &lt;strong&gt;faecal calprotectin test&lt;/strong&gt; - one of the most common non-invasive ways to monitor IBD disease activity in clinic.&lt;/p&gt;
&lt;p&gt;By analysing raw single-cell data, we independently identified the exact genes behind this clinical test - at the resolution of individual cells. That's the kind of validation that confirms the analysis is biologically meaningful, not just a statistical artefact.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-learned---technical-takeaways&quot;&gt;What I learned - technical takeaways&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Batch correction is non-negotiable for multi-sample studies.&lt;/strong&gt; Without Harmony, the donor-to-donor variation would swamp the disease signal. Any &quot;finding&quot; could be explained by technical noise. Harmony is now a standard step in any multi-patient single-cell study.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Choosing highly variable genes with &lt;code&gt;batch_key&lt;/code&gt; matters.&lt;/strong&gt; If you find HVGs without accounting for batches, you risk selecting genes that are variable only because of technical differences between samples. Using &lt;code&gt;batch_key='donor_id'&lt;/code&gt; ensures you're finding genes that are genuinely biologically variable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Clinical relevance is the best validation.&lt;/strong&gt; When your computational analysis independently reproduces a well-established clinical biomarker (faecal calprotectin), it gives you confidence that the pipeline is working correctly and the findings are real.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Public datasets are powerful.&lt;/strong&gt; This entire analysis used freely available data from CZ CELLxGENE - no lab access required. The tools (Scanpy, harmonypy) are free and open source. Computational biology has an exceptionally low barrier to entry compared to wet lab science.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-full-code&quot;&gt;The full code&lt;/h3&gt;
&lt;p&gt;The complete annotated notebook is available on GitHub:&lt;br&gt;
👉 &lt;a rel=&quot;nofollow&quot; href=&quot;http://github.com/Farhan89082/ibd-harmony-integration&quot;&gt;https://github.com/Farhan89082/ibd-harmony-integration&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The README includes all figures, biological interpretation, and instructions for reproducing the analysis from scratch.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-s-next-&quot;&gt;What's next?&lt;/h3&gt;
&lt;p&gt;This project is part of a series of three single-cell RNA sequencing analyses I've built for my computational biology portfolio:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Alzheimer's Disease&lt;/strong&gt; - microglial activation and mitochondrial dysfunction in human brain cells → &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Farhan89082/alzheimers-scrna-analysis&quot;&gt;https://github.com/Farhan89082/alzheimers-scrna-analysis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NSCLC Tumour Microenvironment&lt;/strong&gt; - T cell exhaustion trajectories and macrophage polarisation in lung cancer → &lt;a rel=&quot;nofollow&quot; href=&quot;http://github.com/Farhan89082/nsclc-tumour-microenvironment&quot;&gt;https://github.com/Farhan89082/nsclc-tumour-microenvironment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IBD Harmony Integration&lt;/strong&gt; - this article&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you're a Python developer curious about getting into computational biology, or a biology student learning to code, I hope this walkthrough shows that the barrier is lower than it looks. The tools are excellent, the data is freely available, and the biology is genuinely fascinating.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Have questions about the analysis or want to discuss the methodology? Drop a comment below - I'd love to hear from you.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21912/how-used-python-analyse-000-human-gut-cells-and-uncover-what-makes-crohns-disease-different</guid>
<pubDate>Sat, 04 Jul 2026 20:19:33 +0000</pubDate>
</item>
<item>
<title>T Cells, Tumour Macrophages, and Why Lung Cancer Evades Your Immune System</title>
<link>https://coderlegion.com/21911/t-cells-tumour-macrophages-and-why-lung-cancer-evades-your-immune-system</link>
<description>&lt;p&gt;One of the most frustrating puzzles in cancer biology: some lung cancer patients respond brilliantly to immunotherapy. Others don't respond at all. The tumour microenvironment (TME), the ecosystem of immune, stromal, and cancer cells that surrounds a tumour is a big part of why.&lt;/p&gt;
&lt;p&gt;I wanted to understand what that ecosystem actually looks like at the resolution of individual cells. So I built a single-cell RNA sequencing (scRNA-seq) analysis of non-small cell lung cancer (NSCLC) tissue using Scanpy, working with a publicly available dataset of 111,683 cells from CZ CELLxGENE, spanning 38 distinct cell states.&lt;/p&gt;
&lt;p&gt;Here's what the data revealed about T cell exhaustion, macrophage behaviour, and what makes some tumours more immune-suppressive than others.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;background&quot;&gt;Background&lt;/h3&gt;
&lt;p&gt;NSCLC accounts for approximately 85% of all lung cancer cases. Immune checkpoint inhibitors have transformed treatment for some patients — but a large proportion still don't respond. The leading explanation is that the TME is actively suppressing anti-tumour immunity, rather than the immune system simply being absent.&lt;/p&gt;
&lt;p&gt;To understand this, you need to look not just at whether immune cells are present, but at &lt;strong&gt;what functional state those cells are in&lt;/strong&gt;. That's exactly what single-cell RNA sequencing makes possible.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-dataset&quot;&gt;The Dataset&lt;/h3&gt;
&lt;p&gt;The data comes from a study characterising the cellular and molecular identities of histologic subtypes in lung adenocarcinoma:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;111,683 cells&lt;/strong&gt; after QC (117,266 raw)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;57,398 genes&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;38 distinct cell states&lt;/strong&gt; across the TME&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4 histologic subtypes:&lt;/strong&gt; Acinar/Papillary (A/P), A/P + Solid, Micropapillary (MP), and Solid&lt;/li&gt;
&lt;li&gt;Sequenced with 10× Chromium 3' v3&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-pipeline&quot;&gt;The Pipeline&lt;/h3&gt;
&lt;h4 id=&quot;1-quality-control&quot;&gt;1. Quality Control&lt;/h4&gt;
&lt;p&gt;Cells were filtered to retain those with 200–8,000 detected genes, fewer than 100,000 UMI counts, and less than 25% mitochondrial content. This removed around 5,583 low-quality cells.&lt;/p&gt;
&lt;h4 id=&quot;2-normalisation&quot;&gt;2. Normalisation&lt;/h4&gt;
&lt;p&gt;Standard scRNA-seq approach: normalise to 10,000 counts per cell, log1p transform, identify 2,544 highly variable genes.&lt;/p&gt;
&lt;h4 id=&quot;3-dimensionality-reduction&quot;&gt;3. Dimensionality Reduction&lt;/h4&gt;
&lt;p&gt;PCA (50 components) → neighbourhood graph → UMAP. The resulting embedding resolved all 38 cell states, with clear separation between tumour epithelial cells, immune populations, and stromal cells.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-findings&quot;&gt;The Findings&lt;/h3&gt;
&lt;h4 id=&quot;1-t-cell-exhaustion-is-not-an-end-state---it-s-a-trajectory&quot;&gt;1. T Cell Exhaustion Is Not an End State - It's a Trajectory&lt;/h4&gt;
&lt;p&gt;This was the most biologically interesting result. The CD8+ T cell data revealed three functionally distinct states forming a clear exhaustion trajectory:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;T.CD8.Naive → T.CD8.Predysfunc (8,897 cells) → T.CD8.Exhausted (1,454 cells)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The pre-dysfunctional population is the &lt;em&gt;largest&lt;/em&gt; CD8 group - meaning the majority of cytotoxic T cells in these tumours are actively being pushed toward dysfunction, not yet exhausted but clearly losing their killing capacity.&lt;/p&gt;
&lt;p&gt;The fully exhausted population co-expresses multiple immune checkpoint molecules simultaneously: &lt;code&gt;CTLA4&lt;/code&gt;, &lt;code&gt;LAG3&lt;/code&gt;, &lt;code&gt;TIGIT&lt;/code&gt;, &lt;code&gt;TOX&lt;/code&gt;, &lt;code&gt;ENTPD1&lt;/code&gt;, and &lt;code&gt;CXCL13&lt;/code&gt;. In contrast, cytotoxic T cells express effector molecules (&lt;code&gt;GZMB&lt;/code&gt;, &lt;code&gt;PRF1&lt;/code&gt;, &lt;code&gt;IFNG&lt;/code&gt;) with minimal checkpoint expression. The dotplot makes this contrast striking and clear.&lt;/p&gt;
&lt;h4 id=&quot;2-regulatory-t-cells-outnumber-cytotoxic-t-cells-4-1&quot;&gt;2. Regulatory T Cells Outnumber Cytotoxic T Cells ~4:1&lt;/h4&gt;
&lt;p&gt;Tregs (6,496 cells) vastly outnumber cytotoxic CD8+ T cells (1,688 cells). Tregs express high levels of &lt;code&gt;CTLA4&lt;/code&gt; and &lt;code&gt;TOX&lt;/code&gt;, consistent with their role in actively suppressing anti-tumour immunity. This ratio alone explains a great deal about why these tumours resist immune attack.&lt;/p&gt;
&lt;h4 id=&quot;3-pro-tumour-macrophages-dominate-the-myeloid-compartment&quot;&gt;3. Pro-Tumour Macrophages Dominate the Myeloid Compartment&lt;/h4&gt;
&lt;p&gt;Five macrophage subpopulations were identified:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mac.SPP1&lt;/strong&gt; and &lt;strong&gt;Mac.SPP1.GPNMB&lt;/strong&gt; (~7,054 cells combined) - pro-tumourigenic, SPP1/osteopontin-expressing macrophages associated with poor prognosis in multiple cancer types&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mac.SELENOP&lt;/strong&gt; (3,082 cells) - anti-inflammatory, tissue-resident&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mac.CXCL9&lt;/strong&gt; (1,118 cells) - anti-tumour, recruits cytotoxic T cells&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pro-tumour to anti-tumour macrophage ratio is approximately &lt;strong&gt;6:1&lt;/strong&gt;. Combined with the T cell exhaustion data, this paints a picture of a TME that is actively - and efficiently - suppressing immune responses at multiple levels simultaneously.&lt;/p&gt;
&lt;h4 id=&quot;4-the-paradox-of-solid-tumours&quot;&gt;4. The Paradox of Solid Tumours&lt;/h4&gt;
&lt;p&gt;Solid histologic subtype tumours had the highest immune infiltration (~75% immune cells vs ~63% in acinar/papillary). But solid subtype is also the most aggressive histology.&lt;/p&gt;
&lt;p&gt;This counterintuitive finding is consistent with what's sometimes called &lt;strong&gt;immune suppression rather than immune exclusion&lt;/strong&gt;: more immune cells arrive, but the suppressive environment is so potent that they can't function. High infiltration doesn't automatically mean effective anti-tumour immunity.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-this-means-for-immunotherapy&quot;&gt;What This Means for Immunotherapy&lt;/h3&gt;
&lt;p&gt;The data points to a few specific vulnerabilities worth thinking about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The large &lt;strong&gt;pre-dysfunctional T cell population&lt;/strong&gt; is a potential therapeutic opportunity - these cells aren't fully exhausted yet and might respond to checkpoint blockade&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;6:1 pro-tumour macrophage ratio&lt;/strong&gt; suggests that myeloid reprogramming strategies (not just T cell-targeting therapies) may be needed&lt;/li&gt;
&lt;li&gt;The Treg suppression appears to work partly through &lt;code&gt;CTLA4&lt;/code&gt;, which is why anti-CTLA4 therapy has shown activity in some NSCLC settings&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-learned&quot;&gt;What I Learned&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Subsetting for focused analysis pays off.&lt;/strong&gt; Rather than analysing all 38 cell states at once, isolating the T cell and macrophage compartments separately and running marker analysis on those subsets gave much cleaner, more interpretable results.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Using the authors' cell type annotations directly was the right call.&lt;/strong&gt; For a dataset this complex, re-clustering from scratch would have introduced unnecessary uncertainty. Their labels are validated and biologically grounded.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;With 38 cell states and multiple findings, deciding what to prioritise in the write-up took as long as some analysis steps.&lt;/strong&gt; Storytelling is part of the work.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-code&quot;&gt;The Code&lt;/h3&gt;
&lt;p&gt;Everything is in a fully annotated Jupyter Notebook. Download the H5AD file from CZ CELLxGENE and place it in the &lt;code&gt;data/&lt;/code&gt; folder to run it. &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Farhan89082/nsclc-tumour-microenvironment&quot;&gt;https://github.com/Farhan89082/nsclc-tumour-microenvironment&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The TME is one of the most complex biological systems we can now study at single-cell resolution - and it's increasingly clear that understanding it is key to making immunotherapy work for more patients. Happy to discuss the analysis, the biology, or the pipeline in the comments.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21911/t-cells-tumour-macrophages-and-why-lung-cancer-evades-your-immune-system</guid>
<pubDate>Sat, 04 Jul 2026 19:50:20 +0000</pubDate>
</item>
<item>
<title>How I Mapped Brain Cell Changes in Alzheimer's Disease Using Single-Cell RNA Sequencing</title>
<link>https://coderlegion.com/21910/how-i-mapped-brain-cell-changes-in-alzheimers-disease-using-single-cell-rna-sequencing</link>
<description>&lt;p&gt;Alzheimer's disease affects over 55 million people worldwide, yet the precise molecular changes happening inside individual brain cells remain poorly understood. I wanted to dig into that question - not at the tissue level, but at single-cell resolution.&lt;/p&gt;
&lt;p&gt;So I built a full scRNA-seq analysis pipeline in Python using Scanpy, working with a publicly available dataset of 63,608 nuclei from human prefrontal cortex tissue (sourced from CZ CELLxGENE). The donors spanned three Braak stages: 0 (cognitively normal), 2 (early Alzheimer's), and 6 (severe Alzheimer's).&lt;/p&gt;
&lt;p&gt;Here's what I found and how I found it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-dataset-1&quot;&gt;The Dataset&lt;/h3&gt;
&lt;p&gt;The data came from a study on the molecular characterisation of selectively vulnerable neurons in AD. It covers the superior frontal gyrus, a prefrontal region known to be hit hard by neurodegeneration - and includes seven major brain cell types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Glutamatergic neurons&lt;/li&gt;
&lt;li&gt;GABAergic neurons&lt;/li&gt;
&lt;li&gt;Oligodendrocytes&lt;/li&gt;
&lt;li&gt;OPCs (oligodendrocyte precursor cells)&lt;/li&gt;
&lt;li&gt;Astrocytes&lt;/li&gt;
&lt;li&gt;Microglia&lt;/li&gt;
&lt;li&gt;Endothelial cells&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;31,997 genes. 63,608 cells. Three disease stages. A lot to work with.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-pipeline-1&quot;&gt;The Pipeline&lt;/h3&gt;
&lt;h4 id=&quot;1-quality-control-1&quot;&gt;1. Quality Control&lt;/h4&gt;
&lt;p&gt;No dataset is clean out of the box. I filtered cells to keep only those with between 200 and 6,000 detected genes, and excluded anything with more than 20% mitochondrial gene content (high mitochondrial reads usually signal a dying or damaged cell). This removed around 2,809 low-quality cells.&lt;/p&gt;
&lt;h4 id=&quot;2-normalisation-1&quot;&gt;2. Normalisation&lt;/h4&gt;
&lt;p&gt;Library sizes were normalised to 10,000 counts per cell, followed by log1p transformation, standard practice that makes cells comparable regardless of how deeply they were sequenced. I then identified 5,607 highly variable genes to focus the downstream analysis.&lt;/p&gt;
&lt;h4 id=&quot;3-dimensionality-reduction-1&quot;&gt;3. Dimensionality Reduction&lt;/h4&gt;
&lt;p&gt;PCA (50 components) → neighbourhood graph (10 neighbours, 20 PCs) → UMAP embedding.&lt;/p&gt;
&lt;p&gt;The UMAP is where the biology starts to become visible. All seven cell types separated into distinct clusters, with clear separation between neuronal subtypes and glial populations.&lt;/p&gt;
&lt;h4 id=&quot;4-differential-expression&quot;&gt;4. Differential Expression&lt;/h4&gt;
&lt;p&gt;For the microglial analysis, I used a Wilcoxon rank-sum test comparing AD vs normal microglia, with Benjamini-Hochberg multiple testing correction to control the false discovery rate.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-findings-1&quot;&gt;The Findings&lt;/h3&gt;
&lt;h4 id=&quot;glutamatergic-neurons-are-selectively-depleted&quot;&gt;Glutamatergic Neurons Are Selectively Depleted&lt;/h4&gt;
&lt;p&gt;One of the most striking results: glutamatergic (excitatory) neurons dropped from ~34% of cells in normal tissue to ~30% in AD tissue. This might sound like a small shift, but at the scale of 60,000+ cells it's biologically meaningful and it's consistent with what the literature already tells us about the selective vulnerability of excitatory neurons in AD.&lt;/p&gt;
&lt;h4 id=&quot;alzheimer-s-leaves-a-clear-signature-in-microglia&quot;&gt;Alzheimer's Leaves a Clear Signature in Microglia&lt;/h4&gt;
&lt;p&gt;Microglia are the brain's resident immune cells, and they showed the most dramatic transcriptomic shifts between AD and normal tissue. The differential expression analysis revealed:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Upregulated in AD microglia:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MALAT1&lt;/code&gt; - a long non-coding RNA strongly linked to neuroinflammation&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FTH1&lt;/code&gt; - ferritin heavy chain, pointing to iron dysregulation&lt;/li&gt;
&lt;li&gt;&lt;code&gt;B2M&lt;/code&gt; - beta-2 microglobulin, a known AD biomarker reflecting immune activation&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FOXP1&lt;/code&gt; - a transcription factor tied to microglial activation states&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Downregulated in AD microglia:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MT-CO3&lt;/code&gt;, &lt;code&gt;MT-CO1&lt;/code&gt;, &lt;code&gt;MT-ATP6&lt;/code&gt;, &lt;code&gt;MT-ND2&lt;/code&gt; - mitochondrial complex genes, suggesting impaired energy metabolism in AD-affected microglia&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This pattern is consistent with what's described as disease-associated microglia (DAM) in the literature, a distinct activation state that emerges in neurodegeneration.&lt;/p&gt;
&lt;h4 id=&quot;disease-progression-captured-across-braak-stages&quot;&gt;Disease Progression Captured Across Braak Stages&lt;/h4&gt;
&lt;p&gt;Cells from all three Braak stages were distributed across every cluster in the UMAP. This reflects that AD-associated transcriptomic changes are not confined to one cell type, they propagate across the whole cellular ecosystem as the disease progresses.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-learned-1&quot;&gt;What I Learned&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Memory management matters.&lt;/strong&gt; 60K+ cells × 30K+ genes is a big matrix. Working with sparse AnnData objects and being deliberate about which steps you checkpoint to disk makes a real difference.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cell type annotation is an art.&lt;/strong&gt; The dataset came with pre-annotated cell types, but validating them against canonical marker genes (the dotplot step) is essential and satisfying when the biology confirms itself.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Volcano plots are still one of the most readable ways to communicate differential expression.&lt;/strong&gt; They give you significance and fold change in one glance.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-code-1&quot;&gt;The Code&lt;/h3&gt;
&lt;p&gt;Everything is in a fully annotated Jupyter Notebook. If you want to reproduce the analysis, download the H5AD file from CZ CELLxGENE and drop it in the &lt;code&gt;data/&lt;/code&gt; folder. &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Farhan89082/alzheimers-scrna-analysis&quot;&gt;https://github.com/Farhan89082/alzheimers-scrna-analysis&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you're working with single-cell data or have questions about the pipeline, I'd love to hear from you in the comments. There's something fascinating about watching biology emerge from a matrix of gene counts.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21910/how-i-mapped-brain-cell-changes-in-alzheimers-disease-using-single-cell-rna-sequencing</guid>
<pubDate>Sat, 04 Jul 2026 19:49:43 +0000</pubDate>
</item>
<item>
<title>How Excel is used in Real World Data Analysis</title>
<link>https://coderlegion.com/20789/how-excel-is-used-in-real-world-data-analysis</link>
<description>&lt;h4 id=&quot;excel&quot;&gt;EXCEL&lt;/h4&gt;
&lt;p&gt;This is a spreadsheet software developed by Microsoft used to insert , read and manipulate data.&lt;/p&gt;
&lt;p&gt;This is an example of how an excel page looks like. It is called a workbook&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wwbxva8ehfw7hmgehi0g.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;basics-of-excel&quot;&gt;Basics of excel&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ucg29ftgr4eosadgntub.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Formula bar&lt;/strong&gt; - Area above the grid where the contents or formula of the selected cell appear.&lt;br&gt;
&lt;strong&gt;Column header&lt;/strong&gt; - Is the label at the very top of a column in a spreadsheet, database, or web table.&lt;br&gt;
&lt;strong&gt;Row header&lt;/strong&gt; - Is the first column in a data table that identifies the specific information, category, or ID for the corresponding data to its right.&lt;br&gt;
&lt;strong&gt;Selected cell/Active Cell&lt;/strong&gt; -  Is the currently selected cell in a spreadsheet where you can directly enter data or formulas.&lt;br&gt;
&lt;strong&gt;Sheet tab&lt;/strong&gt; - Tabs at the bottom (Sheet1, Sheet2) that switch between spreadsheets within a workbook.&lt;br&gt;
&lt;strong&gt;Cell&lt;/strong&gt; - A single box where a row and a column intersect eg D9.&lt;br&gt;
&lt;strong&gt;Row&lt;/strong&gt; - Labelled 1,2,3... down the left side horizontally .&lt;br&gt;
&lt;strong&gt;Columns&lt;/strong&gt; - Labelled A,B,C... across the top vertically.&lt;/p&gt;
&lt;h4 id=&quot;ways-in-which-excel-is-used-in-real-world-data-analytics&quot;&gt;Ways in which Excel is used in real world data analytics&lt;/h4&gt;
&lt;h5 id=&quot;1-financial-reporting&quot;&gt;1. Financial reporting&lt;/h5&gt;
&lt;p&gt;Most business uses excel to create financial reports, forecast revenue and analyze profitability. Excel can also help track expenses , manage cash flow and optimize financial decision making.&lt;/p&gt;
&lt;p&gt;This is an example of a profit and loss statement&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ydaymbckch92ztn3n5md.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h5 id=&quot;2-healthcare-amp-research&quot;&gt;2. Healthcare &amp;amp; Research&lt;/h5&gt;
&lt;p&gt;Patients records, Lab results , clinical results data are stored in excel . Lab results can be used to predict future outbreaks of diseases&lt;/p&gt;
&lt;p&gt;This is an example of a medical research KPI dashboard in excell&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iqirjv7oveofjr0uwfvv.webp&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h5 id=&quot;3-marketing-and-sales&quot;&gt;3. Marketing and Sales&lt;/h5&gt;
&lt;p&gt;Excel is used in sales tracking, inventory control,revenue targets, conversion rates and supply chain management. Businesses can use formulas to calculate stock levels, reorder points, and sales growth trends. It helps companies reduce stockouts, prevent overstocking, and improve operational efficiency.&lt;/p&gt;
&lt;p&gt;This is an example of an excel sales forecasting &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zcdath67dwkmjqwmwy1a.webp&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h5 id=&quot;4-business-forecasting-amp-decision-making&quot;&gt;4. Business forecasting &amp;amp; Decision making&lt;/h5&gt;
&lt;p&gt;This is predicting what will happen in the future to be able to make decision based on the prediction.By using historical data and forecasting models, businesses can predict market trends,plan for future growth, assess risk and make data-driven decisions.&lt;/p&gt;
&lt;p&gt;This is an example of a forecast sheet&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4nfloodfc6xqwv8urxsb.webp&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h5 id=&quot;5-automation-amp-productivity&quot;&gt;5. Automation &amp;amp; Productivity&lt;/h5&gt;
&lt;p&gt;Excel automation features like templates and predefined functions help businesses save time and reduce manual errors. It allows companies to automate repetitive tasks such as data entry, invoicing, and payroll processing, increasing overall productivity.&lt;/p&gt;
&lt;p&gt;This  is an example of an automated Periods Table.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j4pa9bjn5hoo46haw5kn.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;formulas-in-excel&quot;&gt;Formulas in Excel&lt;/h4&gt;
&lt;p&gt;In excel they are very many function that makes data analysis simple . Here a few of the main functions one should know.&lt;/p&gt;
&lt;h5 id=&quot;1-sum&quot;&gt;1. Sum&lt;/h5&gt;
&lt;p&gt;Adds all the values in the said range &lt;br&gt;
E.g in the below it add all values from B2,B3,...,B8&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=SUM(B2:B8) 
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;2-average&quot;&gt;2. Average&lt;/h5&gt;
&lt;p&gt;Finds the average of values in the said range&lt;br&gt;
E.g In the below cell it finds the average of all values between cell  D2,D3,...,D22&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=AVERAGE(D2:D22)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;3-max&quot;&gt;3. Max&lt;/h5&gt;
&lt;p&gt;Finds the max number in said range of values&lt;br&gt;
E.g In the below cell it finds the maximum number in all cells from F3,F4,...,F10&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=MAX(F3:F10)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;4-count&quot;&gt;4. Count&lt;/h5&gt;
&lt;p&gt;Count cells with numbers&lt;br&gt;
E.g It returns the number of cells with  numbers&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=COUNT(H2:H10)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;5-filter&quot;&gt;5. Filter&lt;/h5&gt;
&lt;p&gt;Extracts data from a table based on criteria you specify.&lt;br&gt;
E.g Checks the data and instantly spill out a list of only the rows where sales (Column C) are greater than 5000.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=FILTER(A2:C100, C2:C100&amp;gt;5000, &quot;No Sales&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;6-sort&quot;&gt;6. Sort&lt;/h5&gt;
&lt;p&gt;Sorts the contents of a range or array automatically.&lt;br&gt;
E.g Sorts the data range based on the 2nd column, in descending order (highest to lowest).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=SORT(A2:B100, 2, -1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;7-if&quot;&gt;7. IF&lt;/h5&gt;
&lt;p&gt;It checks if a condition is true or false and gives you a different result depending on the outcome.&lt;br&gt;
E.g If a student's grade in cell B2 is 70 or higher, Excel prints &quot;Pass&quot;. If it's 69 or lower, it prints &quot;Fail&quot;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=IF(B2&amp;gt;=70, &quot;Pass&quot;, &quot;Fail&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;8-median&quot;&gt;8. Median&lt;/h5&gt;
&lt;p&gt;Returns the exact middle number in a given set of numbers.&lt;br&gt;
E.g Finds the middle value of all numbers in that range.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=MEDIAN(C2:C50)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;9-mode&quot;&gt;9. Mode&lt;/h5&gt;
&lt;p&gt;Returns the most frequently occurring or repetitive value in a dataset.&lt;br&gt;
E.g It shows you which number is most frequently.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=MODE(D2:D100)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;10-proper&quot;&gt;10. Proper&lt;/h5&gt;
&lt;p&gt;It capitalizes the first letter of every word in a text string and converts all other letters to lowercase.&lt;br&gt;
E.g If cell A2 contains the messy text: &quot;jOHN mCcLAIN&quot; the formula will cleanly output: &quot;John Mcclain&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=PROPER(A2)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;11-countif&quot;&gt;11.COUNTIF&lt;/h5&gt;
&lt;p&gt;Returns the value in a given a range given one condition&lt;br&gt;
E.g How many total deals did the North region close?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=COUNTIF(B2:B100, &quot;North&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;12-countifs&quot;&gt;12. COUNTIFS&lt;/h5&gt;
&lt;p&gt;Returns the value in a given range with more than one condition&lt;br&gt;
E.g Returns the number of deals in the North region are still 'Pending'&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=COUNTIFS(B2:B100, &quot;North&quot;, D2:D100, &quot;Pending&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;13-sumif&quot;&gt;13.SUMIF&lt;/h5&gt;
&lt;p&gt;Adds the values in a range that meet a single, specified condition&lt;br&gt;
E.g Adds the total revenue brought in by the sales rep 'Sarah'?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=SUMIF(A2:A100, &quot;Sarah&quot;, C2:C100)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;14-sumifs&quot;&gt;14.SUMIFS&lt;/h5&gt;
&lt;p&gt;Adds all numbers in a range that meet multiple conditions&lt;br&gt;
E.g Adds total revenue brought in by 'Sarah' for deals larger than $10,000?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=SUMIFS(C2:C100, A2:A100, &quot;Sarah&quot;, C2:C100, &quot;&amp;gt;10000&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;15-averageif&quot;&gt;15. AVERAGEIF&lt;/h5&gt;
&lt;p&gt;Calculates the average of numbers in a range that meet a specific condition&lt;br&gt;
E.g Calculate the average deal size in the South region?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=AVERAGEIF(B2:B100, &quot;South&quot;, C2:C100)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;16-averageifs&quot;&gt;16. AVERAGEIFS&lt;/h5&gt;
&lt;p&gt;Calculates the averages of numbers in a specific range based on more than one conditions.&lt;br&gt;
E.g Calculates the average size of 'Closed' deals in the South region?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=AVERAGEIFS(C2:C100, D2:D100, &quot;Closed&quot;, B2:B100, &quot;South&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;17-round&quot;&gt;17. ROUND&lt;/h5&gt;
&lt;p&gt;Used to round a number to a specified number of digits&lt;br&gt;
E.g A sales commission calculates out to $1,250.4567. You need to round it to standard currency (2 decimal places) for payroll.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=ROUND(1250.4567, 2) returns 1250.46
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;18-roundup&quot;&gt;18. ROUNDUP&lt;/h5&gt;
&lt;p&gt;Used to round a number up&lt;br&gt;
E.g You need to ship items to a client. A box holds 10 items, and they ordered 41 items ($41 / 10 = 4.1$). You can't ship 4.1 boxes; you must round up to ensure you have enough physical boxes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=ROUNDUP(4.1, 0) returns 5.
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;19-int&quot;&gt;19. INT&lt;/h5&gt;
&lt;p&gt;Rounds a number down to the nearest whole interger by removing its decimals&lt;br&gt;
E.g An employee clocked 38.75 hours this week. You want to extract just the full, completed hours they worked.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=INT(38.75) returns 38
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;20-abs&quot;&gt;20.ABS&lt;/h5&gt;
&lt;p&gt;Returns the positive value of a number by removing its negative sign &lt;br&gt;
E.g If a refund is logged as -$450.00, you want to know the absolute dollar volume of the transaction.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=ABS(-450) returns 450
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;21-today-&quot;&gt;21. TODAY()&lt;/h5&gt;
&lt;p&gt;Is a formula that updates the day automatically&lt;br&gt;
E.g Finding out exactly what day it is today to calculate invoice aging.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=TODAY() returns 06/05/2026 (based on today's date)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;22-now&quot;&gt;22. NOW&lt;/h5&gt;
&lt;p&gt;Inserts the current date and time&lt;br&gt;
E.g Stamping a report so stakeholders know exactly what minute the data was pulled from the server.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=NOW() returns 06/05/2026 15:02
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;23-year&quot;&gt;23. YEAR&lt;/h5&gt;
&lt;p&gt;To extract year in a date&lt;br&gt;
E.g You want to group sales by their calendar year.If cell A2 holds 06/05/2026.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=YEAR(A2) returns 2026
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;24-weekday&quot;&gt;24.WEEKDAY&lt;/h5&gt;
&lt;p&gt;To extract the day of the week in excel&lt;br&gt;
E.g Checking if a client submitted a payment on a weekend so you can account for bank processing delays.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;=WEEKDAY(A2) returns 6 (which represents Friday)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;25-weeknum&quot;&gt;25. WEEKNUM&lt;/h5&gt;
&lt;p&gt;Finds the week number of a specific date&lt;br&gt;
E.g &lt;/p&gt;
&lt;p&gt;Excel is a very important tool in Data analysis. We can say it the mother of all since it can do almost all the analysis. Excel needs to be one of the most important tools one has to learn to be able to understand data analysis.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20789/how-excel-is-used-in-real-world-data-analysis</guid>
<pubDate>Wed, 17 Jun 2026 07:34:38 +0000</pubDate>
</item>
<item>
<title>My Data Journey</title>
<link>https://coderlegion.com/20787/my-data-journey</link>
<description>&lt;h6 id=&quot;background-1&quot;&gt;Background&lt;/h6&gt;
&lt;p&gt;As a young kid i realized i had a passion with Mathematics. I would really struggle understanding the other subjects but Math to me was so easy . When i finished highschool the big question was what i wanted to do . I already new that i wanted to do something associated with Math. I did my research and the course that was coming up more frequent was Acturial Science . I applied for the course later on while still doing more research Data science came up and i was curious about it . Looked it up and so that the career opportunities were what i would be intrested . I was later called for Acturial Science but did a course transfer to Data Science&lt;/p&gt;
&lt;h6 id=&quot;life-in-data-science&quot;&gt;Life in Data Science&lt;/h6&gt;
&lt;p&gt;Back to September 2023 and i began my degree in Data Science . At first i was happy with what i was learning and enjoyed . The only challenge i faced is i expected alot of practical work and i only recieved the theory part of most my units. I wanted to learn the theory but i didn't know where to start. I tried out different online contents but i was never satisfied with whatever i was learning . I still was eager to get my hands on real data.As this was the reason i joined Lux academy to get hands on skill on Data Science&lt;/p&gt;
&lt;p&gt;All my best as i begin this journey&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20787/my-data-journey</guid>
<pubDate>Wed, 17 Jun 2026 07:33:46 +0000</pubDate>
</item>
<item>
<title>The Leash That Makes AI Polite</title>
<link>https://coderlegion.com/19996/the-leash-that-makes-ai-polite</link>
<description>&lt;h3 id=&quot;how-a-1951-statistics-formula-quietly-keeps-your-chatbot-from-going-feral&quot;&gt;How a 1951 statistics formula quietly keeps your chatbot from going feral&lt;/h3&gt;
&lt;p&gt;Two ideas sit underneath every modern AI assistant you've ever used. One is a seventy-year-old equation from information theory. The other is the training trick that turned a raw text-prediction engine into something you'd actually want to talk to. They're usually explained separately — but the interesting part is where they meet.&lt;/p&gt;
&lt;h4 id=&quot;first-a-way-to-measure-surprise&quot;&gt;First: a way to measure surprise&lt;/h4&gt;
&lt;p&gt;Start with the &lt;strong&gt;Kullback–Leibler divergence&lt;/strong&gt;. The name is intimidating; the idea isn't. It measures how far apart two sets of expectations are.&lt;/p&gt;
&lt;p&gt;Picture a weather forecaster. Reality is 70% sunny, 30% rain. But you wake up convinced it's a coin flip — 50/50 — and dress accordingly. KL divergence is the price you pay, on average, for believing the wrong thing. Get caught in a drizzle, get overdressed in the sun: small mismatches, small cost. If instead you'd believed it was &lt;em&gt;always&lt;/em&gt; sunny and walked straight into a downpour, the cost would be enormous.&lt;/p&gt;
&lt;p&gt;That's the whole concept. Two distributions — how things actually are, and what you assumed — and a single number for the gap between them.&lt;/p&gt;
&lt;p&gt;Three things are worth keeping in your pocket. It's zero only when your belief is perfect. It can climb toward infinity when you're badly wrong, so it's &lt;em&gt;not&lt;/em&gt; a tidy score that sits between 0 and 1. And it's lopsided — being too optimistic and being too pessimistic don't cost the same amount. It isn't a neutral ruler; it's a directed penalty for surprise. (If you want the math laid out gently, with code, the DataCamp tutorial below is a good next stop.)&lt;/p&gt;
&lt;h4 id=&quot;then-teaching-a-model-some-taste&quot;&gt;Then: teaching a model some taste&lt;/h4&gt;
&lt;p&gt;Now the second idea. A raw language model is a spectacularly good guesser of the next word, trained on a firehose of internet text. What it is &lt;em&gt;not&lt;/em&gt; is helpful, honest, or polite. Left alone, it'll happily continue a sentence in whatever direction the statistics pull it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RLHF — Reinforcement Learning from Human Feedback&lt;/strong&gt; — is what fixes that. The recipe, roughly: show humans two answers to the same question, ask which is better, collect thousands of those judgments, and train a second model to predict human taste. Then nudge the language model to chase higher scores from that taste-model. That's how it learns to be useful instead of merely plausible.&lt;/p&gt;
&lt;h4 id=&quot;where-they-meet&quot;&gt;Where they meet&lt;/h4&gt;
&lt;p&gt;Here's the catch. Let a model chase a reward with no restraint, and it cheats. It discovers that flattery scores well, that vague hedging is rarely marked wrong, that &quot;Great question!&quot; pleases the crowd. Unchecked, it drifts into a confident, sycophantic mush — gaming the scoreboard while quietly forgetting how to talk.&lt;/p&gt;
&lt;p&gt;So engineers attach a leash. At every step they ask: how far has the model drifted from its original, pretrained self? That distance is measured with &lt;strong&gt;KL divergence&lt;/strong&gt;. Drift a little — fine. Drift too far, and the penalty pulls it back.&lt;/p&gt;
&lt;p&gt;The polished, agreeable voice of your favorite chatbot is the product of exactly this tension: a reward tugging it toward what people like, and a 1951 formula tugging the other way, whispering &lt;em&gt;don't go feral.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;go-deeper&quot;&gt;Go deeper&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;KL divergence, with intuition and code&lt;/strong&gt; — DataCamp's tutorial: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.datacamp.com/tutorial/kl-divergence&quot;&gt;https://www.datacamp.com/tutorial/kl-divergence&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RLHF, from the ground up&lt;/strong&gt; — Nathan Lambert's &lt;em&gt;Reinforcement Learning from Human Feedback&lt;/em&gt; (free online book): &lt;a rel=&quot;nofollow&quot; href=&quot;https://arxiv.org/abs/2504.12501&quot;&gt;https://arxiv.org/abs/2504.12501&lt;/a&gt; (web version at rlhfbook.com)&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19996/the-leash-that-makes-ai-polite</guid>
<pubDate>Tue, 09 Jun 2026 05:30:00 +0000</pubDate>
</item>
<item>
<title>Lessons from a Kaggle Playground Competition</title>
<link>https://coderlegion.com/19891/lessons-from-a-kaggle-playground-competition</link>
<description>&lt;p&gt;Just participated in this month's Kaggle Tabular Playground challenge focused on Stellar Classification &lt;/p&gt;
&lt;p&gt;The goal was to classify astronomical objects into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;STAR&lt;/li&gt;
&lt;li&gt;GALAXY&lt;/li&gt;
&lt;li&gt;QSO (Quasar)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A few interesting findings from the exploration:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Redshift turned out to be one of the strongest predictive signals.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Color indices such as &lt;code&gt;u-g&lt;/code&gt;, &lt;code&gt;g-r&lt;/code&gt;, and &lt;code&gt;r-i&lt;/code&gt; were often more informative than raw photometric measurements.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A simple ensemble of LightGBM, XGBoost, and CatBoost performed surprisingly well.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Despite a moderately imbalanced dataset (~65% Galaxy, ~20% QSO, ~14% Star), explicit class weighting actually reduced validation performance in my experiments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hyperparameter tuning with Optuna did not outperform a carefully engineered baseline model, reinforcing the importance of feature engineering and domain understanding.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To document the entire process, I created a beginner-friendly guided notebook covering:&lt;/p&gt;
&lt;p&gt;• Dataset understanding&lt;br&gt;
• Exploratory analysis&lt;br&gt;
• Feature engineering&lt;br&gt;
• Categorical encoding&lt;br&gt;
• Stratified K-Fold validation&lt;br&gt;
• LightGBM, XGBoost, and CatBoost&lt;br&gt;
• Model ensembling&lt;br&gt;
• Experimentation with class imbalance and hyperparameter tuning&lt;/p&gt;
&lt;p&gt;If you're learning machine learning, working on tabular data, or interested in astronomy-related datasets, feel free to take a look.&lt;/p&gt;
&lt;p&gt;I'm also happy to discuss alternative approaches, feature engineering ideas, or leaderboard strategies. Constructive feedback and questions are always welcome.&lt;/p&gt;
&lt;p&gt;Notebook Link: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.kaggle.com/code/rahulmish/predicting-stellar-class-beginner-friendly&quot;&gt;Click Here&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19891/lessons-from-a-kaggle-playground-competition</guid>
<pubDate>Sun, 07 Jun 2026 08:44:50 +0000</pubDate>
</item>
<item>
<title>Data Sanitization is a Design Flaw: How Penta-V Prevents Data Pollution at the Hardware-Software</title>
<link>https://coderlegion.com/18183/data-sanitization-is-design-flaw-how-penta-prevents-data-pollution-the-hardware-software</link>
<description>&lt;p&gt;Go to GitHub, search for &quot;data cleaning library,&quot; and you will find thousands of repositories. From &lt;code&gt;Pandas&lt;/code&gt; to &lt;code&gt;Pydantic&lt;/code&gt;, the entire software engineering industry has been conditioned to treat data sanitization as a &lt;strong&gt;reactive, post-facto chore&lt;/strong&gt;. You ingest data, you realize it’s full of missing values, &lt;code&gt;NaN&lt;/code&gt;s, or volatile floating-point noise (&lt;code&gt;Inf&lt;/code&gt;), and then you write heavy, resource-expensive loops to &quot;clean&quot; it.&lt;/p&gt;
&lt;p&gt;Here is the cold, hard truth that legacy architectures refuse to admit: &lt;strong&gt;If your system is spending CPU cycles cleaning data inside its execution core, your architecture has already failed.&lt;/strong&gt; In high-frequency pipelines and autonomous AI environments, traditional cleaning introduces massive latency spikes, destroys CPU cache locality, and leads to &lt;strong&gt;Logic Drift&lt;/strong&gt;—where the state of your system silently decays.&lt;/p&gt;
&lt;p&gt;When we engineered the &lt;strong&gt;Penta-V Kernel (v0.4.0)&lt;/strong&gt;, we didn't build another data-cleaning library. We built &lt;code&gt;src/processing/cleaner.rs&lt;/code&gt; (&lt;code&gt;PentaCleaner&lt;/code&gt;) to prove a different architectural paradigm: &lt;strong&gt;We don't clean data; we structurally prevent data pollution from existing.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-legacy-trap-why-runtime-cleaning-kills-high-frequency-systems&quot;&gt;The Legacy Trap: Why Runtime Cleaning Kills High-Frequency Systems&lt;/h3&gt;
&lt;p&gt;In standard hybrid pipelines (Python bridging into Rust via PyO3), data passed from autonomous AI agents is inherently probabilistic and volatile. &lt;/p&gt;
&lt;p&gt;When a naive system encounters corrupted data at runtime, it usually triggers one of two reactive paths:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Allocation Nightmare:&lt;/strong&gt; It instantiates dynamic buffers on the heap to reconstruct tables (e.g., &lt;code&gt;df.fillna()&lt;/code&gt;), causing memory fragmentation and triggering garbage collection jitter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Unsafe Race:&lt;/strong&gt; It forces raw parallel mutation via &lt;code&gt;unsafe&lt;/code&gt; pointers, risking data races and undefined behavior (UB) under sudden systemic stress.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Penta-V eliminates this by transforming data purification from an operational step into an &lt;strong&gt;Adaptive Geometric Constraint&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-zero-allocation-parallel-extraction-eliminating-memory-debt-&quot;&gt;1. Zero-Allocation Parallel Extraction (Eliminating Memory Debt)&lt;/h3&gt;
&lt;p&gt;Instead of allocating new memory frames to clean data, the &lt;code&gt;PentaCleaner&lt;/code&gt; leverages a highly optimized, lock-free parallel extraction pipeline driven by safe Rust abstractions (&lt;code&gt;Polars&lt;/code&gt; and &lt;code&gt;Rayon&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Look at how the core execution loop handles a contaminated DataFrame:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;// src/processing/cleaner.rs

pub fn geometric_sanitize(df: &amp;amp;mut DataFrame, state: &amp;amp;ProcessingState) -&amp;gt; PolarsResult&amp;lt;()&amp;gt; {
    let pressure = state.data_pressure;

    // SAFE &amp;amp; LOCK-FREE PARALLELISM:
    // We leverage low-level pointer duplication (shallow clones) to parallelize 
    // column purification across all available CPU cores without single-line heap allocations.
    let updated_columns: Vec&amp;lt;Series&amp;gt; = df
        .get_columns()
        .par_iter()
        .map(|series| {
            let mut clned = series.clone(); // Cheap shallow clone (O(1) pointer copy)
            Self::purify_column(&amp;amp;mut clned, pressure);
            clned
        })
        .collect();

    // Reconstruct the DataFrame in a single CPU cycle by consuming the safe vector
    *df = DataFrame::new(updated_columns)?;

    Ok(())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;why-this-is-prevention-not-cleaning-&quot;&gt;Why this is prevention, not cleaning:&lt;/h4&gt;
&lt;p&gt;By enforcing compile-time data aliasing rules, the Rust compiler guarantees that no two threads can cause a data race. The allocation cost is &lt;strong&gt;exactly zero&lt;/strong&gt; because &lt;code&gt;series.clone()&lt;/code&gt; in Polars is merely a shallow reference duplication. The system doesn't wait for data to become a problem; it enforces a strict parallel layout where invalid structures cannot propagate.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-pressure-aware-structural-adaptation-the-anti-fragile-gate-&quot;&gt;2. Pressure-Aware Structural Adaptation (The Anti-Fragile Gate)&lt;/h3&gt;
&lt;p&gt;A static data cleaner is blind. It executes the exact same parsing script whether your server is idling at 5% or burning at 99% capacity. Under extreme load, this blindness causes memory queues to back up, leading to catastrophic system throttles.&lt;/p&gt;
&lt;p&gt;Penta-V introduces &lt;strong&gt;Pressure-Aware Sanitization&lt;/strong&gt;. The kernel dynamically mutates its purification strategy based on the thermodynamic state of the infrastructure (&lt;code&gt;data_pressure&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn purify_column(series: &amp;amp;mut Series, pressure: f64) {
    // Dynamic Strategy Selection based on real-time hardware telemetry
    let strategy = if pressure &amp;gt; 0.8 {
        // High-Intensity: Forward fill maintains the continuous geometric sequence 
        // under extreme load without interrupting the ALU pipeline.
        FillNullStrategy::Forward(None)
    } else {
        // Standard Baseline: Zero-fill enforces an absolute mathematical baseline.
        FillNullStrategy::Zero
    };

    if let Ok(filled) = series.fill_null(strategy) {
        *series = filled;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-engineering-magic-&quot;&gt;The Engineering Magic:&lt;/h4&gt;
&lt;p&gt;When system stress crosses the critical &lt;code&gt;0.8&lt;/code&gt; barrier, the kernel automatically swaps the logic profile. It stops attempting heavy static recalculations and shifts to an &lt;strong&gt;Asymptotic Geometric Sequence&lt;/strong&gt; (&lt;code&gt;Forward Fill&lt;/code&gt;). This prevents the execution queue from stalling, absorbing the kinetic shock of incoming data anomalies in real-time.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-the-structural-immunity-audit-penta-v-vs-the-market&quot;&gt;3. The Structural Immunity Audit: Penta-V vs. The Market&lt;/h3&gt;
&lt;p&gt;To understand why this approach renders traditional cleaning obsolete, we must look at how data corruption behaves across different software layers:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Architectural Metric &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Traditional Cleaners (&lt;code&gt;Pandas&lt;/code&gt; / &lt;code&gt;Pydantic&lt;/code&gt;) &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Native Engines (&lt;code&gt;Polars&lt;/code&gt; Alone) &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Penta-V Kernel Substrate (&lt;code&gt;PentaCleaner&lt;/code&gt;)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;System Interaction&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Reactive (Cleans after corruption infects the memory). &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Pure Processing (Executes instructions blindly). &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Sovereign Prevention&lt;/strong&gt; (Sanitizes at the FFI boundary before core access).&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Hardware Awareness&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Completely Blind (Heavy CPU/Memory overhead). &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Static Multi-threading (Fixed execution path). &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Dynamic Telemetry&lt;/strong&gt; (&lt;code&gt;Pressure-Aware&lt;/code&gt; adaptation under load).&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Memory Delta&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;High (Continuous heap allocations and fragmentation). &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Optimized but subject to continuous runtime re-allocations. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;0.0000 MB Dynamic Delta&lt;/strong&gt; via stack-resident pointer manipulation.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Execution Latency&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Milliseconds ($ms$) — Destroys high-frequency pipelines. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Microseconds ($\mu s$). &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Sub-nanosecond (0.85ns flat)&lt;/strong&gt; via bit-level vector alignment.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion-stop-cleaning-start-hardening-&quot;&gt;Conclusion: Stop Cleaning. Start Hardening.&lt;/h3&gt;
&lt;p&gt;Writing code to &quot;clean&quot; corrupted data is a tacit admission that your system boundaries are porous. In the era of autonomous AI codebases and ultra-low latency requirements, you cannot afford to let bad data enter your core logic and then figure out how to patch it.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;PentaCleaner&lt;/code&gt; module within the &lt;strong&gt;Penta-V Kernel&lt;/strong&gt; shifts the paradigm. By combining the raw parallel power of &lt;code&gt;Polars&lt;/code&gt; and &lt;code&gt;Rayon&lt;/code&gt; with a pressure-sensitive, zero-allocation design, it treats data corruption not as an administrative problem to be logged, but as a &lt;strong&gt;kinetic deficit to be neutralized&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When your system boundaries are architecturally incorruptible, &quot;data cleaning&quot; ceases to be a maintenance cost—&lt;strong&gt;it disappears from existence.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; Full Source &amp;amp; Docs:&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/narukihto/Penta-V-Kernel/tree/Heartbeat&quot;&gt;https://github.com/narukihto/Penta-V-Kernel/tree/Heartbeat&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Crates.io: &lt;a rel=&quot;nofollow&quot; href=&quot;https://crates.io/crates/penta_v_kernel&quot;&gt;https://crates.io/crates/penta_v_kernel&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;PyPI: &lt;a rel=&quot;nofollow&quot; href=&quot;https://pypi.org/project/penta-v-kernel/&quot;&gt;https://pypi.org/project/penta-v-kernel/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Penta-V Kernel (v0.4.0) is compiled, hardened, and testing green at sub-nanosecond intervals. Let’s argue about system architecture in the comments below!&lt;/em&gt; ️⚡&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18183/data-sanitization-is-design-flaw-how-penta-prevents-data-pollution-the-hardware-software</guid>
<pubDate>Mon, 18 May 2026 03:10:10 +0000</pubDate>
</item>
<item>
<title>How to Publish and Share a Power BI Report Online</title>
<link>https://coderlegion.com/17805/how-to-publish-and-share-a-power-bi-report-online</link>
<description>&lt;p&gt;As we continue to explore Data Science and Analytics, this weeks deliverable was to work on an electronics sales dashboard in PowerBI and publish it online. This is a common request in data analytics so in this article we will walk through the full process of publishing a Power BI report to the web, from clicking &quot;Publish&quot; in Desktop to getting a shareable link that anyone can open in a browser. &lt;/p&gt;
&lt;h3 id=&quot;the-project-in-brief&quot;&gt;The Project in Brief&lt;/h3&gt;
&lt;p&gt;Before jumping into publishing, here is a quick overview of what the report contains so you have context for the screenshots:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dataset:&lt;/strong&gt; 100 electronics retail transactions across 10 products, 9 cities (USA and Canada), and 3 sales representatives.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data preparation:&lt;/strong&gt; The raw Excel data was loaded into Power Query where it was cleaned, validated, and normalized. Canadian Dollar transactions were converted to USD at a rate of 0.74. A star schema was built with six dimension tables (Products, Customers, Cities, SalesReps, PaymentMethod, DateTable) linked to a central fact table.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DAX measures:&lt;/strong&gt; Total Revenue, Total Profit, Total Orders, Average Profit Margin, Average Shipping Cost, and a calculated Date Table using the &lt;code&gt;CALENDAR()&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dashboard pages:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Page 1 - Executive Dashboard:&lt;/strong&gt; KPI cards, sales by product, revenue by category, sales rep distribution, payment method breakdown, and interactive slicers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e589dmv8uv0mdr4ha78e.png&quot; alt=&quot;Dashboard page&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Page 2 - Product Analysis:&lt;/strong&gt; Revenue and profit comparison by product, geographic maps, profit margin rankings.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j5o34iss775nefitd6gf.png&quot; alt=&quot;Product Analysis&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Page 3 - Sales Reps and Customers:&lt;/strong&gt; Customer rankings, sales rep performance combo chart, customer age distribution.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8wlrz5yb8al94oi697nb.png&quot; alt=&quot;Customers and Sales&quot;&gt;&lt;/p&gt;
&lt;p&gt;With the report complete in Power BI Desktop, the next step is getting it online.&lt;/p&gt;
&lt;h3 id=&quot;step-1-publish-from-power-bi-desktop&quot;&gt;Step 1: Publish from Power BI Desktop&lt;/h3&gt;
&lt;p&gt;Open your &lt;code&gt;.pbix&lt;/code&gt; file in Power BI Desktop. In the top ribbon, go to &lt;strong&gt;Home&lt;/strong&gt; and click the &lt;strong&gt;Publish&lt;/strong&gt; button on the right side of the toolbar.&lt;/p&gt;
&lt;p&gt;If you are not signed in, Power BI will prompt you to sign in with your Microsoft account. Use the account associated with your Power BI Service subscription.&lt;/p&gt;
&lt;p&gt;Once signed in, a dialog box appears asking you to select a destination workspace. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4hu1vs9dmf66rg97wob.png&quot; alt=&quot;Publish a Report&quot;&gt;&lt;/p&gt;
&lt;p&gt;You will see a list of available workspaces. If you have not created any custom workspaces, you will only see &lt;strong&gt;My workspace&lt;/strong&gt;, which is the default personal workspace that comes with every Power BI account.&lt;/p&gt;
&lt;p&gt;Select the workspace you want and click &lt;strong&gt;Select&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Power BI Desktop will upload the report. This usually takes a few seconds depending on the file size. When it finishes, you get a success confirmation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pyq3un8aijpuf6jhjwjq.png&quot; alt=&quot;Publishing Sucessful&quot;&gt;&lt;/p&gt;
&lt;p&gt;Click the &lt;strong&gt;&quot;Open in Power BI&quot;&lt;/strong&gt; link to jump directly to your report in the browser, or click &lt;strong&gt;Got it&lt;/strong&gt; to stay in Desktop.&lt;/p&gt;
&lt;h3 id=&quot;step-2-verify-in-power-bi-service&quot;&gt;Step 2: Verify in Power BI Service&lt;/h3&gt;
&lt;p&gt;Head over to &lt;a rel=&quot;nofollow&quot; href=&quot;https://app.powerbi.com&quot;&gt;app.powerbi.com&lt;/a&gt; and navigate to the workspace where you published. You should see your report listed there.&lt;/p&gt;
&lt;p&gt;Open the report and spend a few minutes checking everything:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do all visuals render correctly?&lt;/li&gt;
&lt;li&gt;Do the slicers filter across all charts as expected?&lt;/li&gt;
&lt;li&gt;Do navigation buttons (if you added any) work?&lt;/li&gt;
&lt;li&gt;Are the KPI numbers matching what you see in Desktop?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/stdrnp4if6r671ipeqrv.png&quot; alt=&quot;Published Report&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the screenshot above, you can see the report is live in the Power BI Service. The left sidebar shows the Pages panel listing all four pages (Dashboard, Products, Sales Reps and Custom...). The report is fully interactive - slicers, tooltips, and filtering all work just like they do in Desktop.&lt;/p&gt;
&lt;p&gt;Notice the toolbar at the top: &lt;strong&gt;File&lt;/strong&gt;, &lt;strong&gt;Export&lt;/strong&gt;, &lt;strong&gt;Share&lt;/strong&gt;, &lt;strong&gt;Explore&lt;/strong&gt;, &lt;strong&gt;Set alert&lt;/strong&gt;, &lt;strong&gt;Monitor&lt;/strong&gt;, and &lt;strong&gt;Edit&lt;/strong&gt;. These are the options available to you as the report owner. The ones we care about for sharing are &lt;strong&gt;File&lt;/strong&gt;, &lt;strong&gt;Export&lt;/strong&gt;, and &lt;strong&gt;Share&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;step-3-share-the-report&quot;&gt;Step 3: Share the Report&lt;/h3&gt;
&lt;p&gt;This is where it gets interesting, and also where you might run into some friction depending on your account type. There are several ways to share a Power BI report, each with different access requirements.&lt;/p&gt;
&lt;h4 id=&quot;publish-to-web-public-embed-&quot;&gt;Publish to Web (Public Embed)&lt;/h4&gt;
&lt;p&gt;This is the simplest option for making your report accessible to anyone without requiring them to have a Power BI account.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Path:&lt;/strong&gt; File &amp;gt; Embed report &amp;gt; Publish to web (public)&lt;/p&gt;
&lt;p&gt;This generates two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;direct URL&lt;/strong&gt; that opens the report in any browser&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;iframe embed code&lt;/strong&gt; that you can paste into a website, blog, or portfolio page&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The direct link looks something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;https://app.powerbi.com/view?r=eyJrIjoi...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The iframe code looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;iframe
  title=&quot;Electronics Sales Dashboard&quot;
  width=&quot;1140&quot;
  height=&quot;541.25&quot;
  src=&quot;https://app.powerbi.com/view?r=eyJrIjoi...&quot;
  frameborder=&quot;0&quot;
  allowFullScreen=&quot;true&quot;&amp;gt;
&amp;lt;/iframe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anyone with the link can view and interact with the report. No sign-in required.&lt;/p&gt;
&lt;h3 id=&quot;step-4-embed-on-a-website-if-publish-to-web-worked-&quot;&gt;Step 4: Embed on a Website (If Publish to Web Worked)&lt;/h3&gt;
&lt;p&gt;If you got the embed code from &quot;Publish to web&quot;, you can host your report on any website. Here are two common approaches:&lt;/p&gt;
&lt;h4 id=&quot;simple-html-page&quot;&gt;Simple HTML Page&lt;/h4&gt;
&lt;p&gt;Create an &lt;code&gt;index.html&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
    &amp;lt;title&amp;gt;Electronics Sales Dashboard&amp;lt;/title&amp;gt;
    &amp;lt;style&amp;gt;
        body {
            margin: 0;
            font-family: Arial, sans-serif;
            background: #f5f5f5;
        }
        .header {
            background: #1B2A4A;
            color: white;
            padding: 20px 40px;
        }
        .header h1 {
            margin: 0;
            font-size: 24px;
        }
        .header p {
            margin: 8px 0 0;
            font-size: 14px;
            color: #B5D4F4;
        }
        .container {
            max-width: 1200px;
            margin: 24px auto;
            padding: 0 20px;
        }
        iframe {
            width: 100%;
            height: 600px;
            border: none;
            border-radius: 8px;
        }
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div class=&quot;header&quot;&amp;gt;
        &amp;lt;h1&amp;gt;Electronics Sales Dashboard&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;Power BI Report -- January 2026&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;container&quot;&amp;gt;
        &amp;lt;iframe
          title=&quot;Electronics Sales Dashboard&quot;
          src=&quot;YOUR_EMBED_URL_HERE&quot;
          allowFullScreen=&quot;true&quot;&amp;gt;
        &amp;lt;/iframe&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;YOUR_EMBED_URL_HERE&lt;/code&gt; with the actual embed URL from Power BI.&lt;/p&gt;
&lt;h4 id=&quot;host-on-github-pages&quot;&gt;Host on GitHub Pages&lt;/h4&gt;
&lt;p&gt;If your project is already in a GitHub repo, you can host the embed page for free using GitHub Pages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a subfolder in your repo (e.g., &lt;code&gt;electronics-sales/&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Put the &lt;code&gt;index.html&lt;/code&gt; file inside it.&lt;/li&gt;
&lt;li&gt;Push to GitHub.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings &amp;gt; Pages&lt;/strong&gt; in your repo.&lt;/li&gt;
&lt;li&gt;Set the source to deploy from your main branch.&lt;/li&gt;
&lt;li&gt;After a minute or two, your page will be live at &lt;code&gt;https://yourusername.github.io/repo-name/electronics-sales/&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If your repo contains multiple projects, each subfolder with its own &lt;code&gt;index.html&lt;/code&gt; becomes a separate page under the same base URL. You only need to enable GitHub Pages once per repo.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;The publishing workflow in Power BI goes like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Build&lt;/strong&gt; the report in Power BI Desktop.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Publish&lt;/strong&gt; to a workspace in the Power BI Service.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verify&lt;/strong&gt; that everything works in the browser.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Share&lt;/strong&gt; using one of the available methods (Publish to web, direct share, or export).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embed&lt;/strong&gt; on a website or GitHub Pages if you want a standalone hosted version.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I hope you enjoyed this guide. If you would like to learn more about the PowerBI project, the insights gained and the original data used, check out my GitHub repo that contains the project: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/BrianMunene96/LuxDevHQ_Assignments/blob/main/README_PowerBI_Project.md&quot;&gt;Electronics Sale Dashboard&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17805/how-to-publish-and-share-a-power-bi-report-online</guid>
<pubDate>Thu, 14 May 2026 10:11:40 +0000</pubDate>
</item>
<item>
<title>Python in data analytics: a beginners guide</title>
<link>https://coderlegion.com/17804/python-in-data-analytics-a-beginners-guide</link>
<description>&lt;p&gt;Python started as a hobby project. Guido van Rossum spent Christmas break in 1989 writing an interpreter he described as &quot;a little scripting language.&quot; He published it in 1991. That origin matters because it shows how much Python has developed to get to where it is today. Python has grown to be one of the most popular programming language. In data analytics specifically, Python overtook R, SAS, Excel, and other proprietary tools not because it was marketed well, but because analysts kept choosing it for each new problem they encountered.&lt;/p&gt;
&lt;h3 id=&quot;what-python-actually-is&quot;&gt;What Python actually is&lt;/h3&gt;
&lt;p&gt;Python is a general-purpose, interpreted programming language. &quot;Interpreted&quot; means you run code directly without compiling it first, which makes it fast. &quot;General-purpose&quot; means the same language handles web servers, automation, scientific computing, and data analysis.&lt;/p&gt;
&lt;p&gt;The syntax is minimal and indentation defines code blocks instead of brackets. You can often read a Python function and understand what it does even if you have never written Python before. Python is also free, open source, and runs on Windows, Mac, and Linux without modification. That combination was actually decisive in getting Python adopted inside companies where budget and IT policy often block paid software.&lt;/p&gt;
&lt;h3 id=&quot;why-data-analysts-picked-python-over-the-alternatives&quot;&gt;Why data analysts picked Python over the alternatives&lt;/h3&gt;
&lt;p&gt;R was excellent for statistics but hard to learn for people without a formal statistics background. SAS was comprehensive but expensive and proprietary. Excel worked for small datasets but fell apart past a few hundred thousand rows. Python was cheap, readable, and already had libraries capable of serious data work.&lt;/p&gt;
&lt;p&gt;In 2008, Wes McKinney was a quantitative analyst at AQR Capital Management running financial calculations that he found painful to do in existing tools. He built pandas, a library that gave Python a spreadsheet-like structure for data manipulation. It became public in 2009. By 2012, analysts across finance, tech, and academia had adopted it.&lt;/p&gt;
&lt;p&gt;Pandas gave Python something it was missing: a clean way to load a CSV, examine the data, filter rows, group by category, and aggregate columns in a few lines. Before pandas, doing any of that required more setup than most analysts would tolerate.&lt;/p&gt;
&lt;p&gt;Once pandas existed, the rest Python adoption moved quickly. More people used Python for data work, which meant more contributors improving the libraries, which attracted more people. That cycle is still running.&lt;/p&gt;
&lt;h3 id=&quot;the-libraries-worth-knowing&quot;&gt;The libraries worth knowing&lt;/h3&gt;
&lt;p&gt;Python itself does not do statistics or draw charts. The libraries do. Four of them appear in almost every data analytics project.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NumPy&lt;/strong&gt; handles numerical computation. It provides arrays that behave like mathematical vectors and matrices, and operations on those arrays run fast because NumPy's internals are written in C. Most other data libraries sit on top of NumPy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;pandas&lt;/strong&gt; provides the DataFrame, which is the structure data analysts actually work with. Think of it as a table with column names and row labels. You load a file, you get a DataFrame, you clean the data in that DataFrame, and you pass it to a model or a chart.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Matplotlib&lt;/strong&gt; handles visualization. John Hunter created it in 2003 to generate figures in Python, drawing some inspiration from MATLAB. It is verbose by modern standards but configurable down to individual pixels when you need that level of control.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Seaborn&lt;/strong&gt; wraps around Matplotlib and produces statistical charts with less code. If you want a box plot, a correlation heatmap, or a regression line on a scatter plot, Seaborn gets you there faster than raw Matplotlib would.&lt;/p&gt;
&lt;h3 id=&quot;cleaning-data-where-most-of-the-work-goes&quot;&gt;Cleaning data: where most of the work goes&lt;/h3&gt;
&lt;p&gt;Most data analysts spend most of their time on data cleaning or handling data that is broken in some way.&lt;/p&gt;
&lt;p&gt;Missing values are one of the most common problems. A column might have null entries because a form field was optional or because records were merged from different systems. You cannot simply ignore them; they cause errors in calculations and bias in results.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pandas as pd

df = pd.read_csv(&quot;sales_data.csv&quot;)

# Check which columns have missing values and how many
print(df.isnull().sum())

# Drop rows where any value is missing
df_clean = df.dropna()

# Or fill missing values with the column mean
df[&quot;revenue&quot;] = df[&quot;revenue&quot;].fillna(df[&quot;revenue&quot;].mean())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Beyond missing values, you deal with duplicates, incorrect data types (a column that should be numbers stored as text), inconsistent category labels (&quot;New York,&quot; &quot;new york,&quot; &quot;NY&quot;), and dates stored in five different formats within the same column.&lt;/p&gt;
&lt;p&gt;These problems are not glamorous. But fixing them before analysis is the difference between results you can trust and results that only look reasonable.&lt;/p&gt;
&lt;h3 id=&quot;analyzing-data-from-raw-numbers-to-answers&quot;&gt;Analyzing data: from raw numbers to answers&lt;/h3&gt;
&lt;p&gt;Once the data is clean, Python makes it straightforward to answer questions about it. Say you have sales data for a retail chain across 200 stores and three years. You want to know which regions are growing and which are not.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Group sales by region and year, then sum revenue
regional_sales = df.groupby([&quot;region&quot;, &quot;year&quot;])[&quot;revenue&quot;].sum().reset_index()

# Compute year-over-year growth rate
regional_sales[&quot;growth&quot;] = regional_sales.groupby(&quot;region&quot;)[&quot;revenue&quot;].pct_change()

print(regional_sales.sort_values(&quot;growth&quot;, ascending=False).head(10))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It runs and produces a sorted table showing the ten fastest-growing region-year combinations in your dataset.&lt;/p&gt;
&lt;p&gt;Beyond grouping and aggregating, Python handles statistical tests, time series analysis, and correlation matrices. SciPy adds t-tests, chi-squared tests, and regression analysis for more rigorous statistical questions. Scikit-learn adds the full range of machine learning models when the goal shifts from describing what happened to predicting what will happen next.&lt;/p&gt;
&lt;h3 id=&quot;visualizing-data-making-the-analysis-usable&quot;&gt;Visualizing data: making the analysis usable&lt;/h3&gt;
&lt;p&gt;Numbers in a table and numbers in a chart communicate differently. A chart of monthly revenue over three years shows trends, seasonality, and anomalies in ways that scrolling through a spreadsheet does not.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(12, 5))
plt.plot(df[&quot;month&quot;], df[&quot;revenue&quot;])
plt.title(&quot;Monthly revenue, 2022-2024&quot;)
plt.xlabel(&quot;Month&quot;)
plt.ylabel(&quot;Revenue (USD)&quot;)
plt.tight_layout()
plt.savefig(&quot;revenue_trend.png&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Seaborn makes it easier to produce charts that carry statistical meaning. A box plot shows the full distribution of values including outliers. A heatmap shows correlations between many variables at once. A pair plot generates scatter plots for every combination of numerical columns in your dataset, which is often the fastest way to spot patterns worth investigating before you commit to a specific analysis.&lt;/p&gt;
&lt;h3 id=&quot;why-beginners-should-start-with-python&quot;&gt;Why beginners should start with Python&lt;/h3&gt;
&lt;p&gt;The first reason is practical: Python is what the job market wants. Data analyst job postings ask for Python more than any other programming language, by a wide margin in most tech-adjacent industries. The 2023 Kaggle Data Science Survey found that over 87% of respondents use Python regularly in their work.&lt;/p&gt;
&lt;p&gt;The second reason is the feedback loop. Python is interpreted, so you see the result of each line immediately. You write a line, run it, see what happens, and adjust. That makes learning faster and less frustrating than compiled languages where you chase errors through a build process before seeing any output.&lt;/p&gt;
&lt;p&gt;The third reason is the tooling. There is a tone of freely available browser-based Python environment with no local setup required, which means you can practice real data analysis without installing anything. Jupyter Notebooks let you mix code, output, and notes in a single document, which is how most analysts actually share their work.&lt;/p&gt;
&lt;p&gt;This being said, it is important to mention that Python will not teach you statistics, though. You can use it to run a regression without understanding what a regression assumes about your data. The code might run, the output look authoritative, but the result is wrong because the data violated an assumption the analyst did not know to check for. Learning Python alongside statistics, is the right approach.&lt;/p&gt;
&lt;p&gt;Finally, it worth noting that when you run into a problem in Python, someone has usually had it before and written about it. Stack Overflow alone has over 2.2 million Python questions and answers as of 2024. Reviewing other people's code is also a good way to learn and develop your own problem solving skills. &lt;/p&gt;
&lt;p&gt;Start with a real dataset you care about. Load it with pandas. See what is broken. Fix it. Ask a question. Answer it. That is the whole Python loop. &lt;/p&gt;
&lt;p&gt;How far are you in your Python learning journey? Let me know in the comments. &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17804/python-in-data-analytics-a-beginners-guide</guid>
<pubDate>Thu, 14 May 2026 10:10:22 +0000</pubDate>
</item>
<item>
<title>What 500 curated failure pairs actually fix: a breakdown across 3 seeds</title>
<link>https://coderlegion.com/16393/what-500-curated-failure-pairs-actually-fix-a-breakdown-across-3-seeds</link>
<description>&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/namakoo/-curating-python-failures-for-dpo-notes-from-the-rejected-side-2ff0&quot;&gt;the previous post&lt;/a&gt;, I described the curation philosophy for IDFU's rejected-side dataset — why I avoid synthetic bug generation, why stub detection matters, why &quot;honest failures&quot; are hard to come by.&lt;/p&gt;
&lt;p&gt;A few people asked the obvious question: does it work?&lt;/p&gt;
&lt;p&gt;This post is the answer. Not as a marketing pitch, but as a breakdown. Aggregate scores hide more than they reveal, and I want to show what's actually changing under the hood.&lt;/p&gt;
&lt;h3 id=&quot;the-setup&quot;&gt;The setup&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Base model&lt;/strong&gt;: Qwen2.5-Coder-3B-Instruct (trained on 92 programming languages)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Method&lt;/strong&gt;: DPO via TRL with LoRA&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data&lt;/strong&gt;: 500 preference pairs from the IDFU dataset&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Eval&lt;/strong&gt;: HumanEval, pass@1, three random seeds (42, 123, 7)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hardware&lt;/strong&gt;: RTX 4060, single-GPU, ~3-4 hours of training per seed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A note on the data shape, since I left this implicit in the previous post. Each preference pair has a chosen side and a rejected side, both produced inside IDFU. The rejected side is what Part 1 was about — honest failures, curated rather than synthesized. The chosen side comes from IDFU's internal certified pool — samples that passed all of IDFU's validation gates and are tracked separately from the commercially-released portion. (The gate composition itself is proprietary; this article describes only what the pool produces, not how it's filtered.)&lt;/p&gt;
&lt;p&gt;So both sides of the pair are produced and verified by the same internal pipeline, against the same quality bar. The contrast that DPO trains against isn't &quot;model output vs. ideal answer,&quot; it's &quot;honest failure vs. honest success on the same task,&quot; with both sides held to the same internal standard. That's the configuration that produced the numbers below.&lt;/p&gt;
&lt;p&gt;The hyperparameters were deliberately conservative. No sweep, no exotic tricks:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;LoRA: r=16, alpha=32, dropout=0.05
Target modules: q/k/v/o/gate/up/down_proj (Qwen standard)
DPO: beta=0.1
Optimizer: learning_rate=5e-5
Batch: size=1, grad_accum=4 (effective batch 4)
Epochs: 3 (= 375 optimizer steps for 500 pairs)
Quantization: 4-bit NF4 + bf16 compute (bitsandbytes)
gradient_checkpointing=True
max_length=2048, max_prompt_length=512
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three seeds because one seed is a coincidence, two seeds is suggestive, and three seeds with consistent direction starts to look like signal. I'd run more if I could afford the GPU time, but training and data generation share the same card, and curation can't pause for a week.&lt;/p&gt;
&lt;h3 id=&quot;the-aggregate&quot;&gt;The aggregate&lt;/h3&gt;
&lt;p&gt;Pass@1 across seeds:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;seed=42:   84.1%   delta +3.66 pp
seed=123:  84.1%   delta +3.66 pp
seed=7:    83.5%   delta +3.05 pp
mean:      83.94 ± 0.35%   /   +3.46 ± 0.35 pp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two seeds landed on the exact same delta, which I noticed and double-checked because it looked too clean. The HumanEval problem set is small (164 problems), failures move in integer counts, and ties at the same delta happen more often than people expect. Seed=7 was lower but in the same direction.&lt;/p&gt;
&lt;p&gt;Not earth-shattering. But the headline number isn't really the point.&lt;/p&gt;
&lt;h3 id=&quot;where-the-improvement-actually-comes-from&quot;&gt;Where the improvement actually comes from&lt;/h3&gt;
&lt;p&gt;I categorized every HumanEval failure by exception type. Five categories cover everything:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://huggingface.co/datasets/namakoo/idfu-verified-code/resolve/main/failure_breakdown.png&quot; alt=&quot;Failure breakdown — base vs DPO across three seeds&quot;&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Category &lt;/th&gt;&lt;th&gt;Base &lt;/th&gt;&lt;th&gt;DPO (mean ± std) &lt;/th&gt;&lt;th&gt;Δ &lt;/th&gt;&lt;th&gt;Relative&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;ASSERTION_FAIL &lt;/td&gt;&lt;td&gt;23 &lt;/td&gt;&lt;td&gt;18.67 ± 0.58 &lt;/td&gt;&lt;td&gt;-4.33 &lt;/td&gt;&lt;td&gt;&lt;strong&gt;-19%&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;NAME_ERROR &lt;/td&gt;&lt;td&gt;6 &lt;/td&gt;&lt;td&gt;3.67 ± 0.58 &lt;/td&gt;&lt;td&gt;-2.33 &lt;/td&gt;&lt;td&gt;&lt;strong&gt;-39%&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;OTHER_RUNTIME &lt;/td&gt;&lt;td&gt;2 &lt;/td&gt;&lt;td&gt;2.67 ± 0.58 &lt;/td&gt;&lt;td&gt;+0.67 &lt;/td&gt;&lt;td&gt;+33% (n=2)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;SYNTAX_ERROR &lt;/td&gt;&lt;td&gt;0 &lt;/td&gt;&lt;td&gt;0.33 ± 0.58 &lt;/td&gt;&lt;td&gt;+0.33 &lt;/td&gt;&lt;td&gt;(1/3 seeds)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TYPE_ERROR &lt;/td&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;1.00 ± 0.00 &lt;/td&gt;&lt;td&gt;0.00 &lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Total failures&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;32&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;26.34&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;-5.66&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;-18%&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A few things stand out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NAME_ERROR drops 39%.&lt;/strong&gt; This is the cleanest signal. NAME_ERROR on HumanEval is typically the model using a stdlib name without importing it (e.g., &lt;code&gt;math.ceil&lt;/code&gt; without &lt;code&gt;import math&lt;/code&gt;, or &lt;code&gt;re.match&lt;/code&gt; without &lt;code&gt;import re&lt;/code&gt;), or referencing an undefined helper. One concrete case I traced was HumanEval/115 (&lt;code&gt;max_fill&lt;/code&gt;), where the base model called &lt;code&gt;math.ceil&lt;/code&gt; with no import; after DPO training, the model added the import inline. The IDFU rejected pool has a lot of these missing-import patterns, because they're a common failure mode when a model fakes confidence on partial knowledge. The transfer to HumanEval was the most direct prediction I had going in, and it's the one that landed hardest.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ASSERTION_FAIL drops 19%.&lt;/strong&gt; This one I didn't expect, and the mechanism is interesting enough to be worth tracing.&lt;/p&gt;
&lt;p&gt;I went through the 8 newly-passing problems on seed=42 by hand. Six of them were genuine algorithmic improvements — the base model's logic was wrong, the DPO model's logic was right. Standard stuff.&lt;/p&gt;
&lt;p&gt;The other two (HumanEval/116 and /123) had identical algorithmic content between base and DPO outputs. The actual difference was that the base model wrote a self-test at the end of the function (&quot;if this fails, raise&quot;), and that self-test crashed at import time on the test harness, registering as ASSERTION_FAIL. The DPO model produced clean function-only output, no embedded self-tests, so import succeeded and the harness's actual tests ran and passed.&lt;/p&gt;
&lt;p&gt;That's a behavioral artifact, not an algorithmic improvement. It reflects the chosen-side distribution: clean idiomatic Python without embedded test scaffolding. DPO learned to &lt;em&gt;not&lt;/em&gt; tack on self-tests, and two of the eight ASSERTION_FAIL fixes are downstream of that.&lt;/p&gt;
&lt;p&gt;So if I want to be honest about the breakdown: the real algorithmic improvement is roughly +2.4 pp out of the +3.46 pp aggregate. The rest is the model learning to output the kind of thing the chosen pool actually contained. That's still a real and useful effect — nobody wants self-tests injected into their library code — but it's not the same thing as &quot;the model got smarter.&quot;&lt;/p&gt;
&lt;p&gt;This is the kind of detail that aggregate scores erase. I think it matters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TYPE_ERROR doesn't move at all.&lt;/strong&gt; Three seeds, all returning exactly 1 failure. This is one specific HumanEval problem that the base model fails on and the DPO model also fails on, in the same way. DPO didn't touch it, didn't make it worse, didn't make it better. A clean null result.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OTHER_RUNTIME and SYNTAX_ERROR are noise.&lt;/strong&gt; The absolute counts are 0-3 across seeds. A single failure shifting between categories produces large relative percentages on tiny denominators. I'd want a much bigger eval set to say anything about these.&lt;/p&gt;
&lt;h3 id=&quot;what-this-is-and-isn-t&quot;&gt;What this is and isn't&lt;/h3&gt;
&lt;p&gt;What it isn't: a refutation of large-data RLHF. 500 samples is not going to compete with a 50,000-sample run done properly. The improvement is a few percentage points, not transformative.&lt;/p&gt;
&lt;p&gt;What it is, I think: a signal that &lt;em&gt;what&lt;/em&gt; you put in the preference pairs matters more than &lt;em&gt;how much&lt;/em&gt;. AP2O reports up to ~+3% pass@k improvement on coding benchmarks (per the paper's abstract; see &lt;a rel=&quot;nofollow&quot; href=&quot;https://arxiv.org/abs/2510.02393&quot;&gt;arXiv:2510.02393&lt;/a&gt; for the full per-benchmark breakdown). This run got into the same ballpark with 500 pairs. The data efficiency ratio probably doesn't survive scaling — there's almost certainly diminishing returns where the curation work stops paying off and you just need volume. But at the small-data end, where most solo developers actually live, the curation seems to do real work.&lt;/p&gt;
&lt;p&gt;The other thing I take from the breakdown is that the improvements aren't coming from gaming the eval. If 500 pairs of curated data caused the model to start failing in new ways — spike of SYNTAX_ERROR, models refusing to attempt — I'd be worried about distribution collapse. None of that shows up. The two categories that move (NAME_ERROR, ASSERTION_FAIL) move &lt;em&gt;down&lt;/em&gt;. The categories that don't move stay put. That's roughly the shape I'd want.&lt;/p&gt;
&lt;h3 id=&quot;what-i-m-doing-next&quot;&gt;What I'm doing next&lt;/h3&gt;
&lt;p&gt;Currently in production: a Tokenization/BPE-focused specialty pack, ETA a few days. The benchmark methodology used here — 3 seeds, full failure breakdown, manual inspection of the deltas — will be re-run on each new specialty as it ships. I'd rather publish slow and verified than fast and aggregate-only.&lt;/p&gt;
&lt;p&gt;If anyone has run similar breakdowns on their own DPO experiments, I'd genuinely like to compare notes. The thing I can't tell from one run is whether the NAME_ERROR / ASSERTION_FAIL split is specific to IDFU's curation choices or a general property of curated DPO with this kind of chosen-pool composition. That's a question with real implications for what to put in the pairs, and I don't have enough data to answer it alone.&lt;/p&gt;
&lt;h3 id=&quot;the-dataset-2&quot;&gt;The dataset&lt;/h3&gt;
&lt;p&gt;A 100-row sample is up at &lt;a rel=&quot;nofollow&quot; href=&quot;https://huggingface.co/datasets/namakoo/idfu-verified-code&quot;&gt;huggingface.co/datasets/namakoo/idfu-verified-code&lt;/a&gt; if you want to look at what the curated dataset actually looks like. The full set is available there too.&lt;/p&gt;
&lt;p&gt;If you're working on DPO or RLHF training and want to compare notes, find me on Twitter (&lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/namakoo123&quot;&gt;@namakoo123&lt;/a&gt;) or reply here.&lt;/p&gt;
&lt;p&gt;Curation continues. The next snapshot is being collected as you read this.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16393/what-500-curated-failure-pairs-actually-fix-a-breakdown-across-3-seeds</guid>
<pubDate>Sat, 02 May 2026 11:57:46 +0000</pubDate>
</item>
<item>
<title>️ How to Detect Minerals (Au, Cu, Li, Fe, etc.) Using Multispectral &amp; Hyperspectral Remote Sensing</title>
<link>https://coderlegion.com/16188/how-to-detect-minerals-au-cu-li-fe-etc-using-multispectral-hyperspectral-remote-sensing</link>
<description>&lt;p&gt;&lt;em&gt;Gérard Cubaka — Geospatial / AI / Earth Observation&lt;/em&gt;&lt;br&gt;
&lt;em&gt;You can start now detecting minerals using our plateform : &lt;a rel=&quot;nofollow&quot; href=&quot;www.geocongoai.com&quot;&gt;GeoCongo AI&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;introduction-2&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Mineral exploration has been revolutionized by remote sensing. Instead of relying only on fieldwork, geologists can now &lt;strong&gt;detect mineralization zones from satellite imagery&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The key idea is simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Each mineral interacts with electromagnetic radiation differently — creating a unique &lt;strong&gt;spectral signature&lt;/strong&gt; across Visible, NIR, SWIR, and TIR wavelengths.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This article explains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How to detect key minerals (Au, Cu, Fe, Li, etc.) using &lt;strong&gt;multispectral data&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;band ratios and indices&lt;/strong&gt; used in practice&lt;/li&gt;
&lt;li&gt;How &lt;strong&gt;hyperspectral data&lt;/strong&gt; unlocks precise mineral identification&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-core-principle-spectral-signatures&quot;&gt;1. Core Principle: Spectral Signatures&lt;/h3&gt;
&lt;p&gt;Every mineral:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reflects&lt;/strong&gt; certain wavelengths&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Absorbs&lt;/strong&gt; others&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Iron oxides → strong in &lt;strong&gt;visible (red)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Clays → absorption near &lt;strong&gt;2.2 µm (SWIR)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Quartz → better detected in &lt;strong&gt;thermal infrared (TIR)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-2-multispectral-mineral-detection&quot;&gt;️ 2. Multispectral Mineral Detection&lt;/h3&gt;
&lt;p&gt;Multispectral sensors (e.g., ASTER, Landsat 8/9) use &lt;strong&gt;broad bands&lt;/strong&gt;, so we rely on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Band ratios&lt;/li&gt;
&lt;li&gt;Indices&lt;/li&gt;
&lt;li&gt;PCA (Principal Component Analysis)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-1-iron-oxides-fe-mn-ti-&quot;&gt;2.1 Iron Oxides (Fe, Mn, Ti)&lt;/h3&gt;
&lt;p&gt;These are the easiest to detect.&lt;/p&gt;
&lt;h4 id=&quot;characteristics-&quot;&gt;Characteristics:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Absorb &lt;strong&gt;blue&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Reflect &lt;strong&gt;red&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;band-ratios-&quot;&gt;Band Ratios:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Landsat 8:&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;B4 / B2
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ASTER:&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;B2 / B1
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;interpretation-&quot;&gt;Interpretation:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Bright areas → &lt;strong&gt;Hematite / Goethite (gossans)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Often indicate &lt;strong&gt;oxidized ore zones&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-2-hydrothermal-alteration-minerals-au-cu-ag-zn-ni-co-&quot;&gt;2.2 Hydrothermal Alteration Minerals (Au, Cu, Ag, Zn, Ni, Co)&lt;/h3&gt;
&lt;p&gt;You rarely detect the metal directly. Instead, detect &lt;strong&gt;alteration halos&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clays (Al-OH)&lt;/li&gt;
&lt;li&gt;Carbonates (Mg-OH)&lt;/li&gt;
&lt;li&gt;Sulfate alteration (Jarosite, Alunite)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;key-indices-&quot;&gt;Key Indices:&lt;/h4&gt;
&lt;h5 id=&quot;clay-index-al-oh-&quot;&gt;Clay Index (Al-OH)&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;ASTER:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;B4 / B6
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Landsat 8:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;B6 / B7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Indicates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kaolinite, Illite&lt;/li&gt;
&lt;li&gt;Strong indicator of &lt;strong&gt;gold (Au) and copper (Cu)&lt;/strong&gt; systems&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h5 id=&quot;carbonate-chlorite-index-mg-oh-&quot;&gt;Carbonate / Chlorite Index (Mg-OH)&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;ASTER:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;B8 / B9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Useful for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Copper (Cu)&lt;/li&gt;
&lt;li&gt;Zinc (Zn)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h5 id=&quot;jarosite-index-gold-indicator-&quot;&gt;Jarosite Index (Gold Indicator)&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;ASTER:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;B4 / B3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Indicates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Acidic alteration → often linked to &lt;strong&gt;gold deposits&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-2-3-silica-amp-quartz-sio-&quot;&gt;⚪ 2.3 Silica &amp;amp; Quartz (SiO₂)&lt;/h3&gt;
&lt;p&gt;⚠️ Problem:&lt;br&gt;
Quartz has &lt;strong&gt;no strong SWIR absorption&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; Solution: Use Thermal Infrared (TIR)&lt;/p&gt;
&lt;h4 id=&quot;silica-index-&quot;&gt;Silica Index:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;ASTER:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;(B11 * B11) / (B10 * B12)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Detects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Quartz veins&lt;/li&gt;
&lt;li&gt;Silicification zones (important for Au)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-4-mafic-amp-ultramafic-rocks-ni-co-diamond-indicators-&quot;&gt;2.4 Mafic &amp;amp; Ultramafic Rocks (Ni, Co, Diamond Indicators)&lt;/h3&gt;
&lt;p&gt;We detect the &lt;strong&gt;host rocks&lt;/strong&gt;, not the minerals directly.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;indices-&quot;&gt;Indices:&lt;/h4&gt;
&lt;h5 id=&quot;mafic-index-&quot;&gt;Mafic Index:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;ASTER:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;B12 / B13
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;ultramafic-index-&quot;&gt;Ultramafic Index:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;ASTER:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;(B1 + B3) / B2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Indicates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kimberlites → &lt;strong&gt;Diamond exploration&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Ultramafic intrusions → &lt;strong&gt;Nickel, Cobalt&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-5-pegmatites-li-be-nb-&quot;&gt;2.5 Pegmatites (Li, Be, Nb)&lt;/h3&gt;
&lt;p&gt;No direct detection ❌&lt;/p&gt;
&lt;p&gt; Strategy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Detect associated minerals:&lt;/li&gt;
&lt;li&gt;Micas (Lepidolite)&lt;/li&gt;
&lt;li&gt;Feldspars&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clay/mica indices (Al-OH absorption ~2.2 µm)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-2-6-special-cases&quot;&gt;⚫ 2.6 Special Cases&lt;/h3&gt;
&lt;h4 id=&quot;diamond&quot;&gt;Diamond&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Not directly detectable&lt;/li&gt;
&lt;li&gt;Target: &lt;strong&gt;Kimberlite pipes&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Look for:&lt;/li&gt;
&lt;li&gt;Mg-rich signatures&lt;/li&gt;
&lt;li&gt;Circular anomalies&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;-uranium-u-&quot;&gt;☢️ Uranium (U)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Detect indirectly:&lt;/li&gt;
&lt;li&gt;Clay alteration&lt;/li&gt;
&lt;li&gt;Redox boundaries (Fe²⁺ / Fe³⁺)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Radiometric surveys are often better than optical&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-rgb-composite-strategy&quot;&gt;3. RGB Composite Strategy&lt;/h3&gt;
&lt;p&gt;For fast visual interpretation:&lt;/p&gt;
&lt;h4 id=&quot;example-gold-copper-exploration-&quot;&gt;Example (Gold/Copper exploration):&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Channel &lt;/th&gt;&lt;th&gt;Data&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Red &lt;/td&gt;&lt;td&gt;Iron Oxide Ratio&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Green &lt;/td&gt;&lt;td&gt;Clay Index&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Blue &lt;/td&gt;&lt;td&gt;Carbonate Index&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt; Result:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bright composite zones = &lt;strong&gt;high exploration targets&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-hyperspectral-data-the-game-changer-&quot;&gt;4. Hyperspectral Data (The Game Changer)&lt;/h3&gt;
&lt;p&gt;Multispectral = ~10 bands&lt;br&gt;
Hyperspectral = &lt;strong&gt;100–300 narrow bands&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; This allows &lt;strong&gt;true mineral identification&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-1-preprocessing&quot;&gt;4.1 Preprocessing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Atmospheric correction:&lt;/li&gt;
&lt;li&gt;ATREM&lt;/li&gt;
&lt;li&gt;QUAC&lt;/li&gt;
&lt;li&gt;Noise filtering:&lt;/li&gt;
&lt;li&gt;Savitzky-Golay&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-2-endmember-extraction&quot;&gt;4.2 Endmember Extraction&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Pixel Purity Index (PPI)&lt;/li&gt;
&lt;li&gt;n-D Visualizer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Goal:&lt;br&gt;
Extract &lt;strong&gt;pure mineral signatures&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-3-spectral-matching-algorithms&quot;&gt;4.3 Spectral Matching Algorithms&lt;/h3&gt;
&lt;h4 id=&quot;sam-spectral-angle-mapper-&quot;&gt;SAM (Spectral Angle Mapper)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Measures similarity between spectra&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;sff-spectral-feature-fitting-&quot;&gt;SFF (Spectral Feature Fitting)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Matches absorption features precisely&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;-linear-unmixing&quot;&gt;⚖️ Linear Unmixing&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Determines proportions:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Pixel = 30% Quartz + 70% Iron Oxide
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-4-targeting-specific-minerals&quot;&gt;4.4 Targeting Specific Minerals&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Mineral &lt;/th&gt;&lt;th&gt;Strategy&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Li, Be, Nb &lt;/td&gt;&lt;td&gt;Shift in mica absorption (~2200 nm)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;U &lt;/td&gt;&lt;td&gt;Redox zones (Fe²⁺ vs Fe³⁺)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Diamond &lt;/td&gt;&lt;td&gt;Indicator minerals (pyrope, magnesite)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Cu &lt;/td&gt;&lt;td&gt;Malachite spectral signature&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Au &lt;/td&gt;&lt;td&gt;Hydrothermal alteration zones&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-4-5-recommended-tools&quot;&gt;️ 4.5 Recommended Tools&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ENVI&lt;/strong&gt; → Industry standard&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QGIS + EnMAP-Box&lt;/strong&gt; → Open-source alternative&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GeoCongo AI&lt;/strong&gt; → Web plateform&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion-3&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Multispectral remote sensing is powerful for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mapping &lt;strong&gt;alteration zones&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Identifying &lt;strong&gt;exploration targets&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Hyperspectral data enables &lt;strong&gt;direct mineral identification&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;key-takeaways&quot;&gt;Key Takeaways&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You rarely detect metals directly — focus on &lt;strong&gt;alteration minerals&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SWIR (2.1–2.4 µm)&lt;/strong&gt; is critical for clays and hydrothermal systems&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;band ratios&lt;/strong&gt; to enhance signals&lt;/li&gt;
&lt;li&gt;Combine indices into &lt;strong&gt;RGB composites&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Use hyperspectral data for &lt;strong&gt;high-precision exploration&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16188/how-to-detect-minerals-au-cu-li-fe-etc-using-multispectral-hyperspectral-remote-sensing</guid>
<pubDate>Wed, 29 Apr 2026 10:39:31 +0000</pubDate>
</item>
<item>
<title>Before You Trust Linear Regression, Read This</title>
<link>https://coderlegion.com/15303/before-you-trust-linear-regression-read-this</link>
<description>&lt;p&gt;&lt;img src=&quot;http://&quot; alt=&quot;&quot;&gt;Linear regression is often the first model we learn &lt;img src=&quot;http://&quot; alt=&quot;&quot;&gt; and the one we misuse the most.&lt;/p&gt;
&lt;p&gt;Not because it’s complicated, but because it’s &lt;em&gt;too easy to run&lt;/em&gt;.&lt;br&gt;
A few lines of code, a decent score, and we move on.&lt;/p&gt;
&lt;p&gt;But linear regression isn’t just a model.&lt;br&gt;
It quietly assumes a lot about your data.&lt;/p&gt;
&lt;p&gt;And if those assumptions don’t hold, your results might still look fine, just not reliable.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Before trusting a linear regression model, there are four things worth checking:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Linearity&lt;/strong&gt;&lt;br&gt;
Are you actually modeling a straight-line relationship?&lt;br&gt;
If the data is curved and you force a line through it, the model will miss patterns.&lt;br&gt;
A quick scatter plot or residual plot usually tells the story.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Independent Observations&lt;/strong&gt;&lt;br&gt;
Your data points shouldn’t influence each other.&lt;br&gt;
This becomes important in time-based data, where yesterday affects today.&lt;br&gt;
If errors are connected, your model is learning patterns it shouldn’t.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Normality of Errors&lt;/strong&gt;&lt;br&gt;
The residuals (errors) should roughly follow a normal distribution.&lt;br&gt;
This matters if you care about interpreting the model, not just predicting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Homoscedasticity&lt;/strong&gt;&lt;br&gt;
In simple terms, your model should be equally wrong across all values.&lt;br&gt;
If errors grow or shrink in certain regions, the model becomes biased.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The tricky part is that none of these checks stop your model from running.&lt;/p&gt;
&lt;p&gt;You’ll still get predictions.&lt;br&gt;
You’ll still get metrics.&lt;/p&gt;
&lt;p&gt;But without checking these, you’re not really using linear regression, you’re just fitting a line and hoping it works.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;A small habit that changes everything:&lt;/p&gt;
&lt;p&gt;Before tuning the model, spend a few minutes validating these assumptions.&lt;/p&gt;
&lt;p&gt;When was the last time you actually checked them before trusting your model?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.ytimg.com/vi/RmBSXnJHWKw/maxresdefault.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15303/before-you-trust-linear-regression-read-this</guid>
<pubDate>Mon, 20 Apr 2026 14:12:38 +0000</pubDate>
</item>
<item>
<title>Grok in 2026: Powerful, Polarizing, and Hard to Ignore</title>
<link>https://coderlegion.com/14562/grok-in-2026-powerful-polarizing-and-hard-to-ignore</link>
<description>&lt;p&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://blog.akshatuniyal.com&quot;&gt;https://blog.akshatuniyal.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Technical progress, real-time power, and a controversy trail that still raises hard questions. Here’s where Grok actually stands.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There’s no AI story in 2026 quite like Grok’s.&lt;/p&gt;
&lt;p&gt;On paper, it is one of the most ambitious AI products in the market. Strong benchmark scores, a real-time information advantage that very few rivals can match, serious computing infrastructure, and a release cadence that barely slows down. xAI has been moving fast — sometimes faster than its critics are comfortable with.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/82oxuclg3ppog7xdmqtn.png&quot; alt=&quot;Grok Colossus&quot;&gt;&lt;/p&gt;
&lt;p&gt;Off paper, the story has been far messier. Grok has been tied to a string of controversies: harmful outputs, questions around system-level moderation choices, and image-generation incidents that triggered regulatory scrutiny in multiple countries.&lt;/p&gt;
&lt;p&gt;And yet — people keep using it. Developers keep benchmarking it. The US Department of Defense integrated it into select classified networks. xAI’s valuation climbed into the hundreds of billions. None of that happens if the model is just hype.&lt;/p&gt;
&lt;p&gt;So what is Grok, really? A serious contender with distinctive strengths, or a product still carrying unresolved trust questions? At this point, probably both. Let’s dig in.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;from-chatbot-to-colossus-how-fast-grok-has-moved&quot;&gt;From Chatbot to Colossus: How Fast Grok Has Moved&lt;/h3&gt;
&lt;p&gt;Grok launched in November 2023 as a beta on X (formerly Twitter), accessible only to paid users. It was honest about what it was: an early product with two months of training behind it, designed to answer almost anything with a bit of wit and a rebellious streak.&lt;/p&gt;
&lt;p&gt;That version feels like ancient history now.&lt;/p&gt;
&lt;p&gt;By July 2025, xAI had released Grok 4 and Grok 4 Heavy, trained on the Colossus supercomputer cluster — at the time housing around 200,000 GPUs in Memphis, Tennessee. Grok 4 Heavy became the first model to achieve a near-passing score on &lt;strong&gt;Humanity’s Last Exam&lt;/strong&gt;, widely regarded as the hardest multi-domain benchmark ever constructed. Musk claimed on the launch stream that the model “&lt;em&gt;is smarter than almost all graduate students in all disciplines simultaneously&lt;/em&gt;.” That’s the kind of sentence that’s easy to dismiss as hype, except the benchmark results were genuinely hard to argue with.&lt;/p&gt;
&lt;p&gt;Then came the 4.x series. Grok 4.1 in November 2025 cut hallucination rates from 12% down to around 4% — a 65% reduction that meaningfully changed the enterprise conversation around the model. Grok 4.20 Beta followed in February 2026 with improved instruction following, LaTeX rendering for scientific outputs, and a multi-agent architecture. By March 2026, Grok 4.20 Beta 2 was live with five further improvements (e.g., &quot;including enhanced vision capabilities and multi-image rendering&quot;).&lt;/p&gt;
&lt;p&gt;To put that in perspective: the pace of improvement from Grok 1 to Grok 4 Heavy is genuinely one of the more impressive model trajectories in AI right now. Very few labs have moved this fast on core capability benchmarks in such a short window.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&quot;The pace of iteration is unusually fast, even by current frontier-model standards.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That speed comes with trade-offs, some of which we’ll get to. But from a pure capability trajectory, xAI’s progress over 18 months has been extraordinary.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;grok-s-clearest-edge-real-time-intelligence&quot;&gt;Grok’s Clearest Edge: Real-Time Intelligence&lt;/h3&gt;
&lt;p&gt;If there is one thing that most clearly separates Grok from other frontier models, it is this: &lt;strong&gt;it is built around what is happening right now&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Most major AI assistants still depend on a training cutoff and then use search or retrieval layers to stay current. That can work well, but it usually feels like an added layer rather than the core product experience.&lt;/p&gt;
&lt;p&gt;Grok is different in that respect. It is deeply integrated with X and can draw on a platform that produces hundreds of millions of posts each day. Breaking news, live reactions, market chatter, sports conversations, memes, and the texture of the internet in motion — this is where Grok feels unusually native.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/30wxknwolo5nitlmhhxh.png&quot; alt=&quot;Grok X platform&quot;&gt;&lt;/p&gt;
&lt;p&gt;For certain use cases, this is a meaningful advantage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Journalists&lt;/strong&gt; and &lt;strong&gt;researchers&lt;/strong&gt; tracking breaking stories&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Market analysts&lt;/strong&gt; who need to know what people are saying about a stock, right now&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Social media managers&lt;/strong&gt; monitoring brand sentiment in real time&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Anyone who needs to understand what’s actually trending vs. what was trending last month&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Few major models offer this kind of live social-context access so natively. And it matters more than it may sound on paper. A lot of real-world information needs are time-sensitive. Being able to answer ‘what are people saying about this right now?’ is a meaningful product advantage, even if freshness does not always guarantee accuracy.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-musk-ecosystem-play&quot;&gt;The Musk Ecosystem Play&lt;/h3&gt;
&lt;p&gt;One of the more underappreciated parts of Grok’s story is how it sits inside a much larger infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;xAI&lt;/strong&gt; was brought together with &lt;strong&gt;SpaceX&lt;/strong&gt; in February 2026, putting &lt;strong&gt;Grok&lt;/strong&gt; inside a much larger ecosystem that also touches &lt;strong&gt;Tesla&lt;/strong&gt;, &lt;strong&gt;Starlink&lt;/strong&gt;, &lt;strong&gt;Neuralink&lt;/strong&gt;, and &lt;strong&gt;X&lt;/strong&gt;. That is not just a corporate footnote. It suggests access to a broader strategic stack:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tesla’s fleet data&lt;/strong&gt; — millions of miles of real-world video, feeding into vision and robotics training&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Starlink’s satellite network&lt;/strong&gt; — potentially bringing AI inference to places that have never had reliable internet&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;X’s social graph&lt;/strong&gt; — the real-time pulse of global conversation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optimus robot integration&lt;/strong&gt; — xAI is already using Grok’s reasoning to power humanoid robots&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;US Department of Defense contracts&lt;/strong&gt; — Grok was integrated into select classified and unclassified military networks in January 2026&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mp36ieplb488wqupd99e.png&quot; alt=&quot;Musk Ecosystem&quot;&gt;&lt;/p&gt;
&lt;p&gt;The DoD integration is particularly notable. It represents a level of institutional trust that usually takes time to build. At the same time, it has drawn criticism from people who believe a model with Grok’s public controversy history warrants closer scrutiny before being embedded in government systems. Both realities can be true at once.&lt;/p&gt;
&lt;p&gt;There’s also the financial picture: a pre-merger valuation of around $230 billion, now part of a combined SpaceX-xAI entity valued at over $1 trillion, with backing from Nvidia, AMD, Sequoia, a16z, BlackRock, and Fidelity. That’s not a scrappy startup anymore. That’s a serious institution with the resources to match.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&quot;Very few AI companies have this kind of cross-industry data and distribution story. Whether that becomes a lasting moat or a governance headache is still an open question.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;where-grok-actually-performs-well&quot;&gt;Where Grok Actually Performs Well&lt;/h3&gt;
&lt;p&gt;Enough big picture. What does Grok actually do well in practice?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real-time research and news analysis&lt;/strong&gt;&lt;br&gt;
This is probably Grok’s clearest practical strength. If your question touches something that happened recently, Grok’s X integration can give it a real edge on freshness and signal detection. The output is not always clean — X is fast, not always reliable — but in terms of immediacy, Grok is unusually strong.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Coding and technical reasoning&lt;/strong&gt;&lt;br&gt;
Grok 4 Heavy benchmarks exceptionally well on coding tasks. The multi-agent architecture in the 4.20 series, where multiple AI agents collaborate on complex problems, has been particularly well received by developers working on larger codebases. The hallucination reduction in 4.1 also made a meaningful difference for technical use cases where wrong answers have real costs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Internet culture and tone&lt;/strong&gt;&lt;br&gt;
This sounds minor but it’s genuinely useful in practice. Grok gets internet humour, meme references, and the texture of online conversation in a way that more formally trained models sometimes miss. That makes it particularly good for content creators, social media work, and anyone who needs writing that feels alive rather than polished-but-sterile.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Long-context tasks&lt;/strong&gt;&lt;br&gt;
Grok 4 supports very large context windows — in practice useful for things like feeding in entire codebases, long research papers, or extended document sets that would overwhelm smaller windows. This is becoming table stakes for frontier models, but Grok handles it well.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-reality-check-growth-pains-amp-the-safety-evolution&quot;&gt;The Reality Check: Growth Pains &amp;amp; The Safety Evolution&lt;/h3&gt;
&lt;p&gt;Any fair assessment of Grok also has to account for the friction. xAI’s tendency to ship fast and iterate in public has come with some very visible growing pains.&lt;/p&gt;
&lt;p&gt;Over the last 18 months, Grok has gone through a number of public incidents — from system prompt leaks tied to political misinformation concerns to the 2025 &quot;MechaHitler&quot; episode, and later the &quot;digital undressing&quot; controversy that drew regulatory scrutiny from the EU and UK.&lt;/p&gt;
&lt;p&gt;By March 2026, the fallout had moved well beyond scrutiny. The UK ICO, Ireland's DPC, Canada's Privacy Commissioner, and Ofcom had all opened formal investigations into xAI over AI-generated harmful imagery. A Tennessee lawsuit alleging Grok had generated sexual images of minors added a legal dimension that no amount of product iteration can paper over. This is no longer just a safety story — it's an active legal exposure story.&lt;/p&gt;
&lt;p&gt;What is also worth noting is that xAI has not treated these issues as background noise. It has tried to translate some of those lessons into product and architecture changes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;From Chaos to Context&lt;/strong&gt;: The 4.1 update was more than a routine patch; it was a focused attempt to improve stability, and xAI said it reduced hallucination rates by roughly 65%.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Multi-Agent Guardrail&lt;/strong&gt;: The current 4.20 series moved toward a multi-agent setup intended to add more internal checks and balances around reasoning and safety.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Institutional Vetting&lt;/strong&gt;: While regulators were asking questions, the US Department of Defense was also doing its own due diligence, eventually integrating Grok into select classified networks in early 2026. That suggests at least some institutions see the trust picture as improving, even if concerns remain.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The story of Grok is not just about a model that stumbled in public. It is also about a model being refined in one of the most visible real-world AI testing grounds. Is it perfect? No. But the pace at which xAI is trying to tighten capability and safety together is part of the story too.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;where-does-grok-sit-in-the-current-ai-landscape-&quot;&gt;Where Does Grok Sit in the Current AI Landscape?&lt;/h3&gt;
&lt;p&gt;By the numbers, Grok 4 Heavy is clearly one of the strongest models in the world. The Humanity’s Last Exam performance, the hallucination reduction, the LMArena visibility — these are not imaginary. The technical progress is real.&lt;/p&gt;
&lt;p&gt;But the current AI landscape is crowded with genuinely strong models. GPT-4o remains the most versatile general-purpose assistant for most professional workflows. Claude has built a strong reputation for writing quality, long-context reasoning, and the kind of calm, deliberate approach to complex tasks that developers value. Gemini has deep Google ecosystem integration and strong multimodal performance. DeepSeek has raised questions about what’s possible at much lower cost.&lt;/p&gt;
&lt;p&gt;Grok’s clearest advantages are real-time information access and the broader Musk ecosystem around it. Its clearest concerns are around guardrails, rollout discipline, and the trust questions that come with a documented history of controversial outputs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where Grok wins&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Real-time research and social listening&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Coding and technical tasks, especially complex multi-step workflows&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Users deeply embedded in the X and Tesla ecosystems&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Applications where cultural relevance and internet-native tone matter&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;High-stakes benchmark performance in controlled environments&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Where the competition still leads&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Enterprise deployments where reliability and trust matter more than raw performance&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Long-form writing with consistent voice and quality&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Workflows requiring deep Google or Microsoft ecosystem integration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Regulated industries where guardrail robustness is non-negotiable&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Teams where the AI safety and controversy track record is a dealbreaker&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-comes-next&quot;&gt;What Comes Next&lt;/h3&gt;
&lt;p&gt;xAI has been open about its ambitions. Musk has publicly suggested a meaningful chance of reaching the world’s first AGI with upcoming models — which may prove visionary, promotional, or a bit of both. The Colossus supercomputer is reportedly continuing to scale. Grok Imagine, the video generation product, released an improved version in February 2026 with full text-to-video and video editing capabilities, positioning Grok as more than a chatbot.&lt;/p&gt;
&lt;p&gt;The SpaceX tie-up also creates a bigger strategic story: an AI company with potential access to satellite infrastructure for global inference, automotive data from one of the world’s largest vehicle fleets, and robotics integration through Optimus. Whether that becomes a durable advantage or creates larger governance challenges is still unclear.&lt;/p&gt;
&lt;p&gt;One signal worth noting: Grok 5 was publicly confirmed for Q1 2026 by Musk himself. That window has passed. xAI now points to Q2 2026, with the model reportedly carrying 6 trillion parameters and training on Colossus 2 — a 1-gigawatt supercluster in Memphis. For a company that prides itself on shipping fast, a missed self-imposed deadline is worth flagging. It doesn't change the capability story, but it's a useful data point on the gap between Musk's timelines and xAI's actual cadence.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;What seems certain is that xAI will keep shipping. They’ve demonstrated that convincingly.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Grok is one of the most technically impressive and most debated AI stories of the moment.&lt;/p&gt;
&lt;p&gt;The capabilities are real. The real-time intelligence advantage is real. The benchmark performance is real. The ecosystem play is real.&lt;/p&gt;
&lt;p&gt;And the improvement arc is worth stating plainly: from a two-month-old beta in 2023 to near-passing on the hardest AI benchmark ever built in under two years. Whatever else you think about Grok, that trajectory is genuinely remarkable.&lt;/p&gt;
&lt;p&gt;So are the controversies, the guardrail questions, and the trust gap that can emerge when a model advances this quickly in public.&lt;/p&gt;
&lt;p&gt;If you need real-time intelligence, are building on X’s ecosystem, or are doing heavy technical work where raw model performance is the primary criterion — Grok deserves a serious look. It might be the best tool for your specific job.&lt;/p&gt;
&lt;p&gt;If you are building for regulated industries, enterprise environments where reliability is non-negotiable, or any setting where harmful outputs would carry serious consequences, this history deserves careful weight.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&quot;The model closest to the live internet is also the one with the most unresolved story. And that’s exactly what makes it worth watching.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;— What’s your experience with Grok? Has it earned your trust yet, or are you still watching from the sidelines? Drop it in the comments below.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;about-the-author&quot;&gt;About the Author&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://akshatuniyal.com&quot;&gt;Akshat Uniyal&lt;/a&gt;&lt;/strong&gt; writes about Artificial Intelligence, engineering systems, and practical technology thinking.&lt;br&gt;
Explore more articles at &lt;a rel=&quot;nofollow&quot; href=&quot;https://blog.akshatuniyal.com&quot;&gt;https://blog.akshatuniyal.com&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14562/grok-in-2026-powerful-polarizing-and-hard-to-ignore</guid>
<pubDate>Sun, 12 Apr 2026 13:15:18 +0000</pubDate>
</item>
<item>
<title>Pandas 3.0's PyArrow String Revolution: A Deep Dive into Memory and Performance</title>
<link>https://coderlegion.com/14212/pandas-3-0s-pyarrow-string-revolution-a-deep-dive-into-memory-and-performance</link>
<description>&lt;h3 id=&quot;introduction-3&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Pandas 3.0 made a game-changing decision: &lt;strong&gt;PyArrow-backed strings are now the default&lt;/strong&gt;. Instead of storing strings as Python objects (the old &lt;code&gt;object&lt;/code&gt; dtype), pandas now uses Apache Arrow's columnar format with the new &lt;code&gt;string[pyarrow]&lt;/code&gt; dtype.&lt;/p&gt;
&lt;p&gt;But here's the question that matters: &lt;strong&gt;How much does this new string dtype actually improve performance and memory usage in real-world scenarios?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To find out, I ran comprehensive benchmarks across diverse datasets and common string operations. The results? &lt;strong&gt;51.8% memory savings&lt;/strong&gt; on average, with operations running &lt;strong&gt;2-27x faster&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This isn't a theoretical improvement, it's a fundamental shift in how pandas handles string data.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-results-summary-dashboard&quot;&gt;The Results: Summary Dashboard&lt;/h3&gt;
&lt;p&gt;Let me start with the headline numbers, then we'll dive into how I got them.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/robot010/Dev-Post-Code/blob/main/pandas-memory-saving/visualizations/summary_dashboard.png?raw=true&quot; alt=&quot;Result Summary&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;the-four-key-metrics&quot;&gt;The Four Key Metrics&lt;/h4&gt;
&lt;h4 id=&quot;1-51-8-memory-savings&quot;&gt;1. 51.8% Memory Savings&lt;/h4&gt;
&lt;p&gt;Across all test datasets, the new PyArrow string dtype used half the memory of the old object dtype. This isn't a marginal improvement, it's transformative for memory-constrained environments.&lt;/p&gt;
&lt;h4 id=&quot;2-6-17x-average-operation-speedup&quot;&gt;2. 6.17x Average Operation Speedup&lt;/h4&gt;
&lt;p&gt;String operations aren't just more memory-efficient: they're dramatically faster. On average, operations like &lt;code&gt;str.lower()&lt;/code&gt;, &lt;code&gt;str.contains()&lt;/code&gt;, and &lt;code&gt;str.len()&lt;/code&gt; run 6x faster with PyArrow strings.&lt;/p&gt;
&lt;p&gt;Some operations are even more impressive:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;str.len()&lt;/code&gt;: 27x faster&lt;/li&gt;
&lt;li&gt;&lt;code&gt;str.startswith()&lt;/code&gt;: 16x faster&lt;/li&gt;
&lt;li&gt;&lt;code&gt;str.endswith()&lt;/code&gt;: 15x faster&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;3-889-mb-total-memory-saved&quot;&gt;3. 889 MB Total Memory Saved&lt;/h4&gt;
&lt;p&gt;Across our test datasets (totaling 645 MB on disk), we saved nearly &lt;strong&gt;1 GB of RAM&lt;/strong&gt; in memory. For a real data pipeline processing dozens of datasets, this compounds quickly.&lt;/p&gt;
&lt;h4 id=&quot;4-memory-overhead-the-game-changer&quot;&gt;4. Memory Overhead: The Game Changer&lt;/h4&gt;
&lt;p&gt;The bottom chart reveals something crucial about how pandas handles strings:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Old string dtype (object):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CSV files on disk: 645 MB&lt;/li&gt;
&lt;li&gt;Loaded into pandas: 1,714 MB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory overhead: 165.7%&lt;/strong&gt; (more than doubles!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;New string dtype (PyArrow):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CSV files on disk: 645 MB&lt;/li&gt;
&lt;li&gt;Loaded into pandas: 825 MB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory overhead: 27.9%&lt;/strong&gt; (minimal overhead)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What does this mean?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When pandas reads a CSV file, it doesn't just store the raw bytes: it creates in-memory data structures for fast operations. The old object dtype was incredibly inefficient, essentially duplicating string data multiple times. The new PyArrow string dtype keeps overhead minimal with a smarter memory layout.&lt;/p&gt;
&lt;p&gt;This is the difference between pandas 2's Python-object approach and pandas 3's columnar Arrow approach.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-methodology-why-5-different-datasets-&quot;&gt;The Methodology: Why 5 Different Datasets?&lt;/h3&gt;
&lt;p&gt;Now that you've seen the results, let me explain how I tested this. Real-world data comes in many shapes and sizes. A single benchmark on one type of data wouldn't tell the whole story.&lt;/p&gt;
&lt;p&gt;That's why I created &lt;strong&gt;5 distinct datasets&lt;/strong&gt;, each representing common patterns you'll encounter in production:&lt;/p&gt;
&lt;h4 id=&quot;1-low-cardinality-dataset-1m-rows-&quot;&gt;1. &lt;strong&gt;Low Cardinality Dataset&lt;/strong&gt; (1M rows)&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; Repeated categorical values like product categories, status codes, regions, and priorities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; This is typical of business data - think order statuses, customer segments, or department codes. The same values repeat millions of times.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example columns:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;category&lt;/code&gt;: &quot;Electronics&quot;, &quot;Clothing&quot;, &quot;Food&quot; (10 unique values)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;status&lt;/code&gt;: &quot;pending&quot;, &quot;completed&quot;, &quot;failed&quot; (4 unique values)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-high-cardinality-dataset-1m-rows-&quot;&gt;2. &lt;strong&gt;High Cardinality Dataset&lt;/strong&gt; (1M rows)&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; Mostly unique strings like user IDs, email addresses, and session tokens.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; When every row is different (like customer emails or transaction IDs), pandas can't use simple optimizations. This tests worst-case scenarios.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example columns:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;user_id&lt;/code&gt;: &quot;USER_00000001&quot;, &quot;USER_00000002&quot;... (1M unique)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;email&lt;/code&gt;: &quot;user123@example45.com&quot; (1M unique)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;3-mixed-string-lengths-dataset-1m-rows-&quot;&gt;3. &lt;strong&gt;Mixed String Lengths Dataset&lt;/strong&gt; (1M rows)&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; A combination of short codes (2-5 chars), medium names (20-50 chars), and long descriptions (100-300 chars).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Real data isn't uniform. You might have product codes next to customer addresses next to order notes. This tests how pandas handles variable-length strings.&lt;/p&gt;
&lt;h4 id=&quot;4-dataset-with-nulls-1m-rows-&quot;&gt;4. &lt;strong&gt;Dataset With Nulls&lt;/strong&gt; (1M rows)&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; Data with missing values (10-33% nulls in different columns).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Messy data is reality. How does pandas 3.0 handle missing string data compared to pandas 2?&lt;/p&gt;
&lt;h4 id=&quot;5-large-dataset-10m-rows-&quot;&gt;5. &lt;strong&gt;Large Dataset&lt;/strong&gt; (10M rows)&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; A scaled-up version to test performance at scale.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Memory savings that look good at 1M rows might behave differently at 10M rows. This validates the findings scale linearly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;memory-savings-by-dataset-type&quot;&gt;Memory Savings by Dataset Type&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/robot010/Dev-Post-Code/blob/04343be535204b9093df487d1bcfb921d9d00f2e/pandas-memory-saving/visualizations/memory_comparison.png?raw=true&quot; alt=&quot;Memory Consumption Comparison&quot;&gt;&lt;/p&gt;
&lt;p&gt;The memory savings from PyArrow strings vary significantly by dataset characteristics:&lt;/p&gt;
&lt;h4 id=&quot;best-case-low-cardinality-data--71-6-&quot;&gt;&lt;strong&gt;Best Case: Low Cardinality Data (-71.6%)&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;When data has &lt;strong&gt;repeated values&lt;/strong&gt; (like categories), PyArrow strings shine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Object dtype: 219 MB&lt;/li&gt;
&lt;li&gt;PyArrow string dtype: 62 MB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Savings: 71.6%&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;worst-case-mixed-string-lengths--30-6-&quot;&gt;&lt;strong&gt;Worst Case: Mixed String Lengths (-30.6%)&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Variable-length strings see smaller (but still significant) savings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Object dtype: 383 MB&lt;/li&gt;
&lt;li&gt;PyArrow string dtype: 266 MB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Savings: 30.6%&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-pattern&quot;&gt;&lt;strong&gt;The Pattern&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Notice how savings correlate with &lt;strong&gt;data characteristics&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Repeated values&lt;/strong&gt; (low cardinality) → Best savings (64-72%)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unique values&lt;/strong&gt; (high cardinality) → Good savings (53-55%)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Variable length&lt;/strong&gt; (mixed sizes) → Moderate savings (31%)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; PyArrow strings help everywhere, but they're &lt;em&gt;especially&lt;/em&gt; powerful for categorical-like data.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;performance-operation-specific-speedups&quot;&gt;Performance: Operation-Specific Speedups&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/robot010/Dev-Post-Code/blob/main/pandas-memory-saving/visualizations/operation_speedup_heatmap.png?raw=true&quot; alt=&quot;Operation Speedup Heatmap&quot;&gt;&lt;/p&gt;
&lt;p&gt;This heatmap shows how much &lt;strong&gt;faster&lt;/strong&gt; PyArrow strings are compared to object dtype for common string operations (values &amp;gt; 1.0 mean PyArrow is faster).&lt;/p&gt;
&lt;h4 id=&quot;the-fastest-operations&quot;&gt;&lt;strong&gt;The Fastest Operations&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;str.len()&lt;/code&gt;: 10-27x faster&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;str.startswith()&lt;/code&gt; and &lt;code&gt;str.endswith()&lt;/code&gt;: 11-18x faster&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;str.contains()&lt;/code&gt;: 3-5x faster&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;str.split()&lt;/code&gt;: 1-8x faster&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;the-pattern-1&quot;&gt;&lt;strong&gt;The Pattern&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Read operations (like &lt;code&gt;len()&lt;/code&gt;, &lt;code&gt;startswith()&lt;/code&gt;) → Massive speedups (10-27x)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;These operations just examine existing data without modification&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Transform operations (like &lt;code&gt;replace()&lt;/code&gt;, &lt;code&gt;split()&lt;/code&gt;) → Good speedups (2-5x)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;These operations create new data, which limits the performance gains&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-trade-off-csv-loading-time&quot;&gt;The Trade-off: CSV Loading Time&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/robot010/Dev-Post-Code/blob/main/pandas-memory-saving/visualizations/load_time_comparison.png?raw=true&quot; alt=&quot;Load Time Comparison&quot;&gt;&lt;/p&gt;
&lt;p&gt;There's no such thing as a free lunch. While PyArrow strings save memory and run operations faster, &lt;strong&gt;loading CSV files is 9%-61% slower&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;why-the-slowdown-&quot;&gt;Why the Slowdown?&lt;/h4&gt;
&lt;p&gt;When pandas reads a CSV with PyArrow strings enabled:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It parses the text (same as before)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It converts strings to PyArrow's columnar format&lt;/strong&gt; (extra step)&lt;/li&gt;
&lt;li&gt;This conversion involves building dictionary encodings and optimized memory structures&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pandas is doing &lt;strong&gt;more work upfront&lt;/strong&gt; to enable better performance downstream.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real-world impact:&lt;/strong&gt; On our 10M row dataset, the difference is &lt;strong&gt;1.63s vs 2.02s&lt;/strong&gt;, an extra 0.4 seconds for 10 million rows. For many data pipelines, this upfront cost might be negligible compared to the 2-27x speedup in subsequent operations.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;pros-and-cons-should-you-adopt-pyarrow-strings-&quot;&gt;Pros and Cons: Should You Adopt PyArrow Strings?&lt;/h3&gt;
&lt;h4 id=&quot;benefits-of-pyarrow-string-dtype&quot;&gt;&lt;strong&gt;Benefits of PyArrow String Dtype&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Massive Memory Savings (30-72%)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dramatically Faster String Operations (2-27x)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Minimal Memory Overhead (28% vs 166%)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Modern Data Ecosystem Integration&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;trade-offs-to-consider&quot;&gt;&lt;strong&gt;Trade-offs to Consider&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Slower CSV Loading (9-61% slower)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Initial data ingestion takes longer&lt;/li&gt;
&lt;li&gt;May impact workflows that repeatedly load small files&lt;/li&gt;
&lt;li&gt;The trade-off: slower start, much faster operations&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Behavioral Changes&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;String dtype behaves differently from object dtype in edge cases&lt;/li&gt;
&lt;li&gt;Need to update code that explicitly checks for &lt;code&gt;object&lt;/code&gt; dtype&lt;/li&gt;
&lt;li&gt;Testing required for migration&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;the-recommendation&quot;&gt;&lt;strong&gt;The Recommendation&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;For most data workflows, &lt;strong&gt;PyArrow strings are a clear win&lt;/strong&gt;. The memory and performance benefits far outweigh the trade-offs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consider staying with object dtype if:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You rarely work with string columns&lt;/li&gt;
&lt;li&gt;Your datasets easily fit in memory&lt;/li&gt;
&lt;li&gt;Load time is critical and you rarely perform string operations&lt;/li&gt;
&lt;li&gt;You have legacy code that's deeply coupled to object dtype behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Definitely adopt PyArrow strings if:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You process large datasets with text data&lt;/li&gt;
&lt;li&gt;String operations are a significant part of your workflow&lt;/li&gt;
&lt;li&gt;Memory is a constraint in your environment&lt;/li&gt;
&lt;li&gt;You're building production data pipelines&lt;/li&gt;
&lt;li&gt;You work with modern data tools (Parquet, Arrow, DuckDB, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion-4&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Our comprehensive analysis across &lt;strong&gt;5 diverse datasets&lt;/strong&gt; and &lt;strong&gt;15+ string operations&lt;/strong&gt; conclusively shows that PyArrow-backed strings deliver transformative improvements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;51.8% average memory savings&lt;/strong&gt; across all dataset types&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;6.17x average operation speedup&lt;/strong&gt; for string operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Minimal memory overhead&lt;/strong&gt; (28% vs 166% with Python objects)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PyArrow strings aren't just an incremental improvement, they're a fundamental reimagining of how pandas handles text data. By adopting Apache Arrow's proven columnar format, pandas has joined the modern data ecosystem while delivering massive performance and memory improvements.&lt;/p&gt;
&lt;p&gt;For most data practitioners working with text data, the question isn't &quot;Should I use PyArrow strings?&quot; but rather &quot;How quickly can I migrate?&quot;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Questions or feedback? Feel free to open an issue or contribute to this analysis! The code we used in this analysis has been uploaded to this &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/robot010/Dev-Post-Code/tree/04343be535204b9093df487d1bcfb921d9d00f2e/pandas-memory-saving&quot;&gt;repo&lt;/a&gt;. &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14212/pandas-3-0s-pyarrow-string-revolution-a-deep-dive-into-memory-and-performance</guid>
<pubDate>Wed, 08 Apr 2026 06:01:21 +0000</pubDate>
</item>
<item>
<title>How to Handle NULLs in Snowflake: A Complete Guide!</title>
<link>https://coderlegion.com/14166/how-to-handle-nulls-in-snowflake-a-complete-guide</link>
<description>&lt;p&gt;&lt;em&gt;Dear Readers,&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;NULL in data is like a weed, it can mess up your analysis and calculations, and ultimately bleed the value out of your analytics work. In this article, we’ll dive into what NULL is, why it exists, when it appears, and how to deal with it in Snowflake ?&lt;/p&gt;
&lt;p&gt;So, without further ado, let’s meet this culprit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What NULL?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In SQL, NULL is basically the database equivalent of a shrug.It is a special marker that represents a missing or unknown value. It's not the same as an empty string ('') or the number zero (0). It simply means “I don’t know” or “there’s no value here at all.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why NULL Exists?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;NULL is essential because sometimes your data just doesn’t exist yet, or never will. NULL allows the database to acknowledge the gap without faking a value.&lt;/p&gt;
&lt;p&gt;Eg:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A customer doesn’t have a middle name.&lt;/li&gt;
&lt;li&gt;A product doesn’t have a discount yet.&lt;/li&gt;
&lt;li&gt;A delivery date is unknown at the time of record insertion.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;When NULL Appears?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;NULL occurs anytime there is a lack of value from the user. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Common reasons include:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Optional fields with no provided value.&lt;/li&gt;
&lt;li&gt;Data not yet available (future delivery dates, pending approvals)&lt;/li&gt;
&lt;li&gt;Inapplicable values (someone’s “date of divorce” who never married)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;How to Deal with NULL in Snowflake?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Snowflake provides multiple functions and methods to deal with NULL dataset aka unknown data similar to other databases. I have listed some of them below:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use IS NULL / IS NOT NULL to identify the NULL data.&lt;/li&gt;
&lt;li&gt;Use COALESCE(col, default) or IFNULL(col, default) to replace them in select , joins, aggregates etc&lt;/li&gt;
&lt;li&gt;Use explicit filter on WHERE clauses to include NULL rows as it can can drop NULL rows unexpectedly.&lt;/li&gt;
&lt;li&gt;Use NVL, CASE WHEN, or similar functions to handle them in calculations or strings.&lt;/li&gt;
&lt;li&gt;Comparisons with NULL return UNKNOWN in SQL’s three-valued logic. Use COALESCE or IS NULL checks to avoid surprises.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Eg:&lt;/p&gt;
&lt;p&gt;==Using NULL/NOT NULL &lt;/p&gt;
&lt;p&gt;-- Finds all employees without a manager&lt;br&gt;
SELECT name FROM employees WHERE manager_id IS NULL;&lt;/p&gt;
&lt;p&gt;-- Finds all employees with a manager&lt;br&gt;
SELECT name FROM employees WHERE manager_id IS NOT NULL;&lt;/p&gt;
&lt;p&gt;==Using COALESCE&lt;/p&gt;
&lt;p&gt;-- Calculates total compensation, treating a NULL bonus as 0&lt;/p&gt;
&lt;p&gt;SELECT salary + COALESCE(bonus, 0) AS total_compensation FROM employees&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Trouble NULL Causes:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Comparison Traps&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;NULL comparisons are a common pitfall because standard operators like = or != don’t work as expected. Any comparison involving NULL returns UNKNOWN, not TRUE or FALSE. Use IS NULL or IS NOT NULL to handle the traps.&lt;/p&gt;
&lt;p&gt;Eg:&lt;/p&gt;
&lt;p&gt;--Evaluates to UNKNOWN when &lt;code&gt;NULL = NULL&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;SELECT *&lt;br&gt;
FROM employees&lt;br&gt;
WHERE manager_id = NULL;&lt;/p&gt;
&lt;p&gt;-- The correct way to find employees with a NULL manager_id is:&lt;br&gt;
SELECT *&lt;br&gt;
FROM employees&lt;br&gt;
WHERE manager_id IS NULL;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Math &amp;amp; Concatenation Failures:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;NULL acts like a virus in arithmetic and string concatenation operations. Any operation involving NULL will result in NULL.&lt;/p&gt;
&lt;p&gt;Eg:&lt;/p&gt;
&lt;p&gt;--Return of NULL total+compensation when bonus is NULL:&lt;/p&gt;
&lt;p&gt;SELECT salary + bonus AS total_compensation&lt;br&gt;
FROM employees;&lt;/p&gt;
&lt;p&gt;--The correct way to add bonus is use COALESCE :&lt;/p&gt;
&lt;p&gt;SELECT salary + COALESCE(bonus, 0) AS total_compensation&lt;br&gt;
FROM employees;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Aggregate Surprises:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Aggregate functions like SUM() and AVG() ignore NULL values. This can lead to misleading results if you’re not aware of it.&lt;/p&gt;
&lt;p&gt;Eg:&lt;/p&gt;
&lt;p&gt;--Excludes NULL rows &lt;/p&gt;
&lt;p&gt;SELECT AVG(salary) AS avg_salary, SUM(salary) AS total_salary&lt;br&gt;
FROM employees;&lt;/p&gt;
&lt;p&gt;--Use COALESCE to treat NULL salaries as 0 to get more accurate company-wide average&lt;/p&gt;
&lt;p&gt;SELECT AVG(COALESCE(salary, 0)) FROM employees;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Join Issues:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;NULL values in join keys never match each other. If an employee has a NULL in manager_id, that row won’t link to any id in the employees table even if other employees also have NULL as manager_id.&lt;/p&gt;
&lt;p&gt;Use a LEFT JOIN + COALESCE to tackle the NULL issue.In following demonstrated way, you keep all employees in the output, and NULL managers are clearly labeled without risking false matches.&lt;/p&gt;
&lt;p&gt;Eg:&lt;/p&gt;
&lt;p&gt;--This INNER JOIN excludes employees with no manager_id&lt;/p&gt;
&lt;p&gt;SELECT &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   E.name AS employee,
   M.name AS manager
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;FROM employees AS E&lt;br&gt;
INNER JOIN &lt;br&gt;
employees AS M &lt;br&gt;
ON E.manager_id = M.id;&lt;/p&gt;
&lt;p&gt;--Use COALESCE to include employees without a manager&lt;/p&gt;
&lt;p&gt;SELECT &lt;br&gt;
  E.name AS employee,&lt;br&gt;
  COALESCE(M.name, 'No Manager') AS manager&lt;br&gt;
FROM employees E&lt;br&gt;
LEFT JOIN employees M &lt;br&gt;
  ON E.manager_id = M.id;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5.Logic Surprises&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In SQL’s three-valued logic (TRUE, FALSE, UNKNOWN), an UNKNOWN result in a WHERE clause acts like FALSE and discards the row. However, UNKNOWN can have different effects in other clauses such as CASE.&lt;/p&gt;
&lt;p&gt;Eg:&lt;/p&gt;
&lt;p&gt;--Logic Mishandling for NULL data.&lt;/p&gt;
&lt;p&gt;SELECT&lt;br&gt;
  CASE&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WHEN bonus &amp;gt; 10000 THEN 'High Bonus'
WHEN bonus &amp;gt; 5000 THEN 'Mid Bonus'
ELSE 'Low or No Bonus'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  END AS bonus_category&lt;br&gt;
FROM employees;&lt;/p&gt;
&lt;p&gt;-- Explicitly checks for NULL first&lt;/p&gt;
&lt;p&gt;SELECT&lt;br&gt;
  CASE&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WHEN bonus IS NULL THEN 'No Bonus'
WHEN bonus &amp;gt; 10000 THEN 'High Bonus'
WHEN bonus &amp;gt; 5000 THEN 'Mid Bonus'
ELSE 'Low Bonus'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  END AS bonus_category&lt;br&gt;
FROM employees;&lt;br&gt;
Closing thoughts,&lt;/p&gt;
&lt;p&gt;NULLs aren’t a bug, they’re part of SQL’s design.NULL is tricky. The safest approach is to scan your dataset for every attribute that could be NULL, then apply handling strategies suited to your business context. In Snowflake, a few well-placed COALESCE, IS NULL, and CASE statements can save you from major data quality headaches.&lt;/p&gt;
&lt;p&gt;Hope you enjoyed the article! If you like my writing, feel free to connect with me on Linkedin and follow for more such interesting reads.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14166/how-to-handle-nulls-in-snowflake-a-complete-guide</guid>
<pubDate>Tue, 07 Apr 2026 14:39:38 +0000</pubDate>
</item>
<item>
<title>“Why Averages Lie: Understanding Mean, Median &amp;amp;amp; Mode with Real Data”</title>
<link>https://coderlegion.com/14045/why-averages-lie-understanding-mean-median-amp-amp-mode-with-real-data</link>
<description>&lt;h2 id=&quot;-lecture-1-central-tendency-of-data&quot;&gt;???? Lecture 1 — Central Tendency of Data&lt;/h2&gt;
&lt;h4 id=&quot;vyoma-data-science-initiative-module-2-statistics&quot;&gt;&lt;em&gt;Vyoma Data Science Initiative | Module 2: Statistics&lt;/em&gt;&lt;/h4&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-introduction&quot;&gt;???? Introduction&lt;/h3&gt;
&lt;p&gt;In any dataset, we often face a simple question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Can we represent all this data using a single number?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Whether it’s exam scores, income levels, or age distributions — we try to &lt;strong&gt;summarize complexity into simplicity&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This idea leads us to &lt;strong&gt;central tendency&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-what-is-central-tendency-&quot;&gt;???? What is Central Tendency?&lt;/h3&gt;
&lt;p&gt;Central tendency refers to the &lt;strong&gt;representative value&lt;/strong&gt; of a dataset — the point around which the data tends to cluster.&lt;/p&gt;
&lt;p&gt;It helps answer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What is typical?&lt;/li&gt;
&lt;li&gt;Where does the data concentrate?&lt;/li&gt;
&lt;li&gt;What does this dataset “look like” overall?&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-measures-of-central-tendency&quot;&gt;???? Measures of Central Tendency&lt;/h3&gt;
&lt;h4 id=&quot;1-mean-average-&quot;&gt;1️⃣ Mean (Average)&lt;/h4&gt;
&lt;p&gt;[
\text{Mean} = \frac{\sum x_i}{n}
]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Represents the &lt;strong&gt;balance point&lt;/strong&gt; of the dataset&lt;/li&gt;
&lt;li&gt;Highly sensitive to &lt;strong&gt;outliers&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-median-middle-value-&quot;&gt;2️⃣ Median (Middle Value)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;middle value&lt;/strong&gt; after sorting data&lt;/li&gt;
&lt;li&gt;If even number of values → average of two middle values&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;???? More &lt;strong&gt;robust&lt;/strong&gt; than mean in real-world datasets&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-mode-most-frequent-value-&quot;&gt;3️⃣ Mode (Most Frequent Value)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;The value that appears &lt;strong&gt;most often&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Useful for both &lt;strong&gt;numerical and categorical data&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-when-averages-mislead&quot;&gt;⚠️ When Averages Mislead&lt;/h3&gt;
&lt;p&gt;Consider:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2, 3, 4, 5, 100
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Mean = 22.8 ❌&lt;/li&gt;
&lt;li&gt;Median = 4 ✅&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;???? The mean is distorted by an extreme value.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;-insight&quot;&gt;???? Insight&lt;/h4&gt;
&lt;blockquote&gt;&lt;p&gt;A single number cannot always capture reality.&lt;br&gt;
The choice of measure defines the “truth” you see.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-practical-implementation-python-&quot;&gt;???? Practical Implementation (Python)&lt;/h3&gt;
&lt;p&gt;Using the &lt;strong&gt;Titanic dataset&lt;/strong&gt;, we analyze the &lt;strong&gt;Age&lt;/strong&gt; column.&lt;/p&gt;
&lt;h4 id=&quot;-code&quot;&gt;???? Code&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

data = pd.read_csv(r&quot;C:\Users\praso\vyomadatascience\module02\titanic.csv&quot;)

print(data.head())

print(&quot;Mean Age:&quot;, data[&quot;Age&quot;].mean())
print(&quot;Mode Age:&quot;, data[&quot;Age&quot;].mode())
print(&quot;Median Age:&quot;, data[&quot;Age&quot;].median())
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-optional-visualization&quot;&gt;???? Optional Visualization&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.histplot(data[&quot;Age&quot;].dropna(), kde=True)
plt.title(&quot;Age Distribution (Titanic Dataset)&quot;)
plt.xlabel(&quot;Age&quot;)
plt.ylabel(&quot;Frequency&quot;)
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-dataset-reference&quot;&gt;???? Dataset Reference&lt;/h3&gt;
&lt;p&gt;You can download the Titanic dataset from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Titanic Dataset&lt;/li&gt;
&lt;li&gt;Official Kaggle link: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.kaggle.com/datasets/yasserh/titanic-dataset&quot;&gt;https://www.kaggle.com/datasets/yasserh/titanic-dataset&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(You can place it in your project folder: &lt;code&gt;module02/titanic.csv&lt;/code&gt;)&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-github-repo&quot;&gt;???? GitHub Repo&lt;/h3&gt;
&lt;p&gt;Download the github repo of our course from here :&lt;/p&gt;
&lt;h3 id=&quot;https-github-com-psjdeveloper-vyomadatascience&quot;&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/psjdeveloper/vyomadatascience&quot;&gt;https://github.com/psjdeveloper/vyomadatascience&lt;/a&gt;&lt;/h3&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-final-reflection&quot;&gt;???? Final Reflection&lt;/h3&gt;
&lt;p&gt;Central tendency is not just about computing mean, median, or mode.&lt;/p&gt;
&lt;p&gt;It is about understanding:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How data behaves&lt;/li&gt;
&lt;li&gt;How summaries can distort reality&lt;/li&gt;
&lt;li&gt;And how &lt;strong&gt;interpretation matters more than calculation&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-closing-line&quot;&gt;✍️ Closing Line&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;Data does not speak for itself.&lt;br&gt;
The way we summarize it decides what it says.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14045/why-averages-lie-understanding-mean-median-amp-amp-mode-with-real-data</guid>
<pubDate>Mon, 06 Apr 2026 11:16:25 +0000</pubDate>
</item>
<item>
<title>Mastering Pandas — Part 4: Data Visualization with Matplotlib &amp; Seaborn</title>
<link>https://coderlegion.com/13871/mastering-pandas-part-4-data-visualization-with-matplotlib-seaborn</link>
<description>&lt;h3 id=&quot;from-clean-data-to-clear-insight&quot;&gt;From Clean Data to Clear Insight&lt;/h3&gt;
&lt;p&gt;In Part 3, you learned how to clean messy data and combine multiple sources into one unified DataFrame. Now that your data is ready, the next step is to communicate what's inside it — and nothing communicates data faster or more clearly than a chart.&lt;/p&gt;
&lt;p&gt;This article covers the two most important visualization libraries in Python: &lt;strong&gt;Matplotlib&lt;/strong&gt;, the core engine that powers all plotting, and &lt;strong&gt;Seaborn&lt;/strong&gt;, a higher-level library built on top of it that produces beautiful statistical charts with minimal code. By the end, you'll know which tool to reach for depending on what you need to show.&lt;/p&gt;
&lt;p&gt;We'll use this sample dataset throughout:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

data = {
    'Country':         ['China', 'India', 'USA', 'Brazil', 'UK'],
    'Continent':       ['Asia', 'Asia', 'North America', 'South America', 'Europe'],
    '2022 Population': [1412000000, 1380000000, 331000000, 214000000, 67000000],
    'Area (km2)':      [9597000, 3287000, 9834000, 8516000, 243000]
}
df = pd.DataFrame(data)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-1-matplotlib-the-engine&quot;&gt;Part 1 — Matplotlib: The Engine&lt;/h3&gt;
&lt;h4 id=&quot;what-is-matplotlib-&quot;&gt;What is Matplotlib?&lt;/h4&gt;
&lt;p&gt;Matplotlib is the foundational plotting library in Python. Every chart you create in Python — whether through Pandas, Seaborn, or directly — eventually goes through Matplotlib to render the final output. Think of it as the engine: it handles the low-level work of drawing lines, shapes, colors, axes, and text.&lt;/p&gt;
&lt;p&gt;The submodule you'll use in practice is &lt;code&gt;matplotlib.pyplot&lt;/code&gt;, which gives you a clean, high-level interface without managing every visual detail manually:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import matplotlib.pyplot as plt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Matplotlib works across three styles, each suited to a different situation:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Style &lt;/th&gt;&lt;th&gt;When to use&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;df.plot(kind=&quot;bar&quot;, y=&quot;col&quot;)&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Your data is already in a DataFrame — quickest way&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;plt.plot(xs, ys)&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Working with raw lists, arrays, or math functions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;fig, ax = plt.subplots()&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Multiple subplots or full layout control&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All three ultimately use Matplotlib to render the chart. &lt;code&gt;plt.show()&lt;/code&gt; is always called at the end to display it.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;global-settings-with-plt-rcparams&quot;&gt;Global Settings with &lt;code&gt;plt.rcParams&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;plt.rcParams&lt;/code&gt; is a global settings dictionary. Any value you set here applies to every chart created afterward in the session — so you set it once at the top of your notebook and never repeat it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;plt.rcParams['figure.figsize'] = (12, 5)   # default width and height
plt.rcParams['font.size']      = 13         # default font size
plt.rcParams['figure.dpi']     = 100        # image sharpness
plt.rcParams['lines.linewidth'] = 2         # default line thickness
plt.rcParams['axes.grid']      = True       # show grid on all charts
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;setting-a-visual-theme-with-plt-style-use-&quot;&gt;Setting a Visual Theme with &lt;code&gt;plt.style.use()&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;One line changes the entire look of your charts — colors, background, grid style, and fonts. Call it &lt;strong&gt;before&lt;/strong&gt; your plot, not after.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;plt.style.use(&quot;ggplot&quot;)
df.plot(kind=&quot;bar&quot;, y=&quot;2022 Population&quot;)
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Style &lt;/th&gt;&lt;th&gt;Look&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&quot;ggplot&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Gray background, colored lines — popular R-like style&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&quot;seaborn-v0_8-whitegrid&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Clean, modern, white with grid&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&quot;fivethirtyeight&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Bold, thick lines — news article style&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&quot;dark_background&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Black background&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&quot;bmh&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Soft colors, Bayesian style&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&quot;grayscale&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Shades of gray only&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&quot;tableau-colorblind10&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Colorblind-friendly palette&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Run &lt;code&gt;print(plt.style.available)&lt;/code&gt; to see every available option.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;plotting-from-a-dataframe-with-plot-&quot;&gt;Plotting from a DataFrame with &lt;code&gt;.plot()&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Since Pandas is built on Matplotlib, every DataFrame and Series has a &lt;code&gt;.plot()&lt;/code&gt; method that creates a chart in one line.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df.plot(kind=&quot;bar&quot;, x=&quot;Country&quot;, y=&quot;2022 Population&quot;, figsize=(10, 5), title=&quot;Population by Country&quot;)
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;kind=&lt;/code&gt; parameter selects the chart type. All common types are supported:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;code&gt;kind=&lt;/code&gt; &lt;/th&gt;&lt;th&gt;Chart&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&quot;bar&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Vertical bars&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&quot;barh&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Horizontal bars&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&quot;line&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Line chart (default)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&quot;pie&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Pie chart&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&quot;hist&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Histogram&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&quot;scatter&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Scatter plot&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;&quot;box&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Box plot&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;You can also call the chart type directly as a method — both styles produce identical output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# These two are exactly the same:
df.plot(kind=&quot;bar&quot;, x=&quot;Country&quot;, y=&quot;2022 Population&quot;)
df.plot.bar(x=&quot;Country&quot;, y=&quot;2022 Population&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The direct method style (&lt;code&gt;df.plot.bar()&lt;/code&gt;) is shorter and more common in practice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key parameters you'll use on almost every chart:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Parameter &lt;/th&gt;&lt;th&gt;What it does &lt;/th&gt;&lt;th&gt;Example&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;kind&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Chart type &lt;/td&gt;&lt;td&gt;&lt;code&gt;&quot;bar&quot;&lt;/code&gt;, &lt;code&gt;&quot;line&quot;&lt;/code&gt;, &lt;code&gt;&quot;scatter&quot;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;x&lt;/code&gt; / &lt;code&gt;y&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Columns for axes &lt;/td&gt;&lt;td&gt;&lt;code&gt;x=&quot;Country&quot;&lt;/code&gt;, &lt;code&gt;y=&quot;2022 Population&quot;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;figsize&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Width and height in inches &lt;/td&gt;&lt;td&gt;&lt;code&gt;figsize=(10, 5)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;title&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Chart title &lt;/td&gt;&lt;td&gt;&lt;code&gt;title=&quot;Population by Country&quot;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;color&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Bar/line color &lt;/td&gt;&lt;td&gt;&lt;code&gt;color=&quot;steelblue&quot;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;legend&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Show or hide legend &lt;/td&gt;&lt;td&gt;&lt;code&gt;legend=True&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;xlabel&lt;/code&gt; / &lt;code&gt;ylabel&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Axis labels &lt;/td&gt;&lt;td&gt;&lt;code&gt;xlabel=&quot;Country&quot;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;rot&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Rotation of tick labels &lt;/td&gt;&lt;td&gt;&lt;code&gt;rot=45&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;grid&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Show background grid &lt;/td&gt;&lt;td&gt;&lt;code&gt;grid=True&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;alpha&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Transparency &lt;/td&gt;&lt;td&gt;&lt;code&gt;alpha=0.8&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;bins&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Bin count (histogram only) &lt;/td&gt;&lt;td&gt;&lt;code&gt;bins=20&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df.plot(
    kind=&quot;bar&quot;,
    x=&quot;Country&quot;,
    y=&quot;2022 Population&quot;,
    figsize=(10, 5),
    title=&quot;2022 World Population&quot;,
    color=&quot;steelblue&quot;,
    xlabel=&quot;Country&quot;,
    ylabel=&quot;Population&quot;,
    rot=0,
    grid=True,
    alpha=0.85
)
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;plotting-directly-with-plt-plot-&quot;&gt;Plotting Directly with &lt;code&gt;plt.plot()&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;When your data is not in a DataFrame — raw lists, NumPy arrays, or a mathematical function — use &lt;code&gt;plt.plot()&lt;/code&gt; directly.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np

xs = np.arange(-5, 5, 0.25)
ys = xs ** 2

plt.figure(figsize=(8, 5))
plt.plot(xs, ys, color=&quot;steelblue&quot;, linewidth=2, linestyle=&quot;--&quot;)
plt.title(&quot;Quadratic Function&quot;)
plt.xlabel(&quot;X&quot;)
plt.ylabel(&quot;Y&quot;)
plt.grid(True)
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;Matplotlib doesn't know your data is a curve — it connects the dots you give it with straight lines. When points are closely spaced (as with &lt;code&gt;np.arange&lt;/code&gt; step &lt;code&gt;0.25&lt;/code&gt;), hundreds of short segments visually blend into a smooth curve.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Customizing &lt;code&gt;plt.plot()&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Color
plt.plot(xs, ys, color='red')

# Line style
plt.plot(xs, ys, linestyle='--')    # dashed
plt.plot(xs, ys, linestyle=':')     # dotted
plt.plot(xs, ys, linestyle='-.')    # dash-dot

# Markers at each point
plt.plot(xs, ys, marker='o')        # circles
plt.plot(xs, ys, marker='s')        # squares
plt.plot(xs, ys, marker='^')        # triangles

# Everything combined
plt.plot(xs, ys, color='green', linestyle='--', linewidth=2, marker='o')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want dots without a line, use &lt;code&gt;plt.scatter()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;plt.scatter(xs, ys)
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;decorating-any-chart&quot;&gt;Decorating Any Chart&lt;/h4&gt;
&lt;p&gt;These functions work on any chart — whether drawn through Pandas or directly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;plt.title(&quot;Chart Title&quot;)
plt.xlabel(&quot;X Axis Label&quot;)
plt.ylabel(&quot;Y Axis Label&quot;)
plt.grid(True)
plt.legend()      # shows labels defined with label= on each line
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When plotting multiple lines, use &lt;code&gt;label=&lt;/code&gt; with each &lt;code&gt;plt.plot()&lt;/code&gt; call, then &lt;code&gt;plt.legend()&lt;/code&gt; to display them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;ys1 = xs ** 2
ys2 = xs ** 3

plt.plot(xs, ys1, label=&quot;f(x) = x²&quot;)
plt.plot(xs, ys2, label=&quot;f(x) = x³&quot;, linestyle=&quot;--&quot;)
plt.legend()
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;multiple-subplots-with-object-oriented-style&quot;&gt;Multiple Subplots with Object-Oriented Style&lt;/h4&gt;
&lt;p&gt;When you need more than one chart in the same figure, use the object-oriented style. It gives you full control over each subplot independently.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 1 row, 2 columns — side by side
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

ax1.bar(df[&quot;Country&quot;], df[&quot;2022 Population&quot;], color=&quot;steelblue&quot;)
ax1.set_title(&quot;Population by Country&quot;)
ax1.set_xlabel(&quot;Country&quot;)
ax1.set_ylabel(&quot;Population&quot;)

ax2.scatter(df[&quot;Area (km2)&quot;], df[&quot;2022 Population&quot;], color=&quot;tomato&quot;)
ax2.set_title(&quot;Area vs Population&quot;)
ax2.set_xlabel(&quot;Area (km²)&quot;)
ax2.set_ylabel(&quot;Population&quot;)

plt.tight_layout()
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;fig&lt;/code&gt; is the whole figure container. &lt;code&gt;ax1&lt;/code&gt; and &lt;code&gt;ax2&lt;/code&gt; are the individual plotting areas. The pattern is the same as &lt;code&gt;plt.*&lt;/code&gt; calls, just moved to &lt;code&gt;ax.set_*()&lt;/code&gt; methods.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-2-seaborn-statistical-visualization&quot;&gt;Part 2 — Seaborn: Statistical Visualization&lt;/h3&gt;
&lt;h4 id=&quot;what-is-seaborn-&quot;&gt;What is Seaborn?&lt;/h4&gt;
&lt;p&gt;Seaborn is a library built on top of Matplotlib, designed for statistical data visualization. It produces polished, publication-quality charts with far less code than raw Matplotlib, and works natively with Pandas DataFrames.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import seaborn as sns
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every Seaborn chart follows the same basic structure:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.chart_type(data=df, x='column_name', y='column_name')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;hue=&lt;/code&gt; parameter is Seaborn's most powerful feature — it automatically splits and colors your data by a category column, adding a legend with no extra work:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.scatterplot(data=df, x='Area (km2)', y='2022 Population', hue='Continent')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;global-styling-with-set-theme-&quot;&gt;Global Styling with &lt;code&gt;set_theme()&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Call this once at the top. Every chart afterward follows these settings automatically.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.set_theme(style='whitegrid', palette='deep', font_scale=1.2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;style&lt;/code&gt;&lt;/strong&gt; controls the background:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.set_theme(style='whitegrid')   # white background with grid — most common
sns.set_theme(style='darkgrid')    # dark background with grid
sns.set_theme(style='white')       # white background, no grid
sns.set_theme(style='ticks')       # minimal, axis ticks only
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;palette&lt;/code&gt;&lt;/strong&gt; controls the colors of chart elements:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.set_theme(palette='deep')        # default, rich colors
sns.set_theme(palette='muted')       # softer, less saturated
sns.set_theme(palette='pastel')      # light pastel colors
sns.set_theme(palette='colorblind')  # accessible for colorblind readers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;font_scale&lt;/code&gt;&lt;/strong&gt; scales all text up or down:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.set_theme(font_scale=1.5)    # larger text
sns.set_theme(font_scale=0.8)    # smaller text
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To apply a style to one chart only without changing global settings:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with sns.axes_style('whitegrid'):
    sns.barplot(data=df, x='Continent', y='2022 Population')
    plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To reset everything back to Matplotlib defaults: &lt;code&gt;sns.reset_defaults()&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;relational-charts-showing-relationships-between-numbers&quot;&gt;Relational Charts — Showing Relationships Between Numbers&lt;/h4&gt;
&lt;p&gt;Use these when you want to see how two numeric columns relate to each other.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;scatterplot()&lt;/code&gt;&lt;/strong&gt; — each row becomes one dot on the chart:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Basic
sns.scatterplot(data=df, x='Area (km2)', y='2022 Population')

# Colored by continent
sns.scatterplot(data=df, x='Area (km2)', y='2022 Population', hue='Continent')
plt.title(&quot;Area vs Population&quot;)
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;lineplot()&lt;/code&gt;&lt;/strong&gt; — connects points with a line. Best for time series or ordered data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.lineplot(data=df, x='Country', y='2022 Population', hue='Continent')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;relplot()&lt;/code&gt;&lt;/strong&gt; — a single figure-level function for both scatter and line. Use &lt;code&gt;kind=&lt;/code&gt; to switch:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.relplot(data=df, x='Area (km2)', y='2022 Population', kind='scatter', hue='Continent')
sns.relplot(data=df, x='Country', y='2022 Population', kind='line')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;distribution-charts-showing-how-values-are-spread&quot;&gt;Distribution Charts — Showing How Values Are Spread&lt;/h4&gt;
&lt;p&gt;Use these to understand the shape and spread of a numeric column.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;histplot()&lt;/code&gt;&lt;/strong&gt; — bars showing how many values fall within each range:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Basic histogram
sns.histplot(data=df, x='2022 Population')

# With smooth density curve on top
sns.histplot(data=df, x='2022 Population', kde=True)

# Split by category
sns.histplot(data=df, x='2022 Population', hue='Continent')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;kdeplot()&lt;/code&gt;&lt;/strong&gt; — a smooth density curve, more refined than a histogram:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.kdeplot(data=df, x='2022 Population', hue='Continent')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a 2D density plot showing where two numeric columns overlap:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.kdeplot(data=df, x='Area (km2)', y='2022 Population')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;rugplot()&lt;/code&gt;&lt;/strong&gt; — adds small tick marks along an axis showing where individual data points are. Typically layered on top of a kdeplot:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.kdeplot(data=df, x='2022 Population')
sns.rugplot(data=df, x='2022 Population')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ecdfplot()&lt;/code&gt;&lt;/strong&gt; — shows the cumulative distribution: what percentage of values fall below each point:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.ecdfplot(data=df, x='2022 Population')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;displot()&lt;/code&gt;&lt;/strong&gt; — one function to rule all distribution charts. Use &lt;code&gt;kind=&lt;/code&gt; to switch:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.displot(data=df, x='2022 Population', kind='hist', hue='Continent')
sns.displot(data=df, x='2022 Population', kind='kde')
sns.displot(data=df, x='2022 Population', kind='ecdf')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;categorical-charts-comparing-groups&quot;&gt;Categorical Charts — Comparing Groups&lt;/h4&gt;
&lt;p&gt;Use these when one axis is a category and the other is numeric.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;barplot()&lt;/code&gt;&lt;/strong&gt; — shows the &lt;strong&gt;mean&lt;/strong&gt; value per category, with confidence interval bars:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.barplot(data=df, x='Continent', y='2022 Population', hue='Continent')
plt.title(&quot;Average Population by Continent&quot;)
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;Note: &lt;code&gt;barplot()&lt;/code&gt; shows the &lt;em&gt;mean&lt;/em&gt;, not the raw values. If you want raw counts by category, use &lt;code&gt;countplot()&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;countplot()&lt;/code&gt;&lt;/strong&gt; — counts how many rows belong to each category:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.countplot(data=df, x='Continent')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;boxplot()&lt;/code&gt;&lt;/strong&gt; — shows median, quartiles, and outliers. A fast way to spot unusual values:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.boxplot(data=df, x='Continent', y='2022 Population')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;violinplot()&lt;/code&gt;&lt;/strong&gt; — like a boxplot, but also shows the full distribution shape on both sides. More informative for larger datasets:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.violinplot(data=df, x='Continent', y='2022 Population')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;stripplot()&lt;/code&gt;&lt;/strong&gt; — shows every individual data point as a dot per category. Useful when you want to see actual values rather than a summary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.stripplot(data=df, x='Continent', y='2022 Population', hue='Continent')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;swarmplot()&lt;/code&gt;&lt;/strong&gt; — same as stripplot but repositions overlapping points so each one is visible:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.swarmplot(data=df, x='Continent', y='2022 Population')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;pointplot()&lt;/code&gt;&lt;/strong&gt; — shows the mean per category as a dot, connected by lines across categories. Good for spotting trends:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.pointplot(data=df, x='Continent', y='2022 Population', hue='Continent')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;catplot()&lt;/code&gt;&lt;/strong&gt; — one function for all categorical charts. Use &lt;code&gt;kind=&lt;/code&gt; to switch between them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.catplot(data=df, x='Continent', y='2022 Population', kind='bar')
sns.catplot(data=df, x='Continent', y='2022 Population', kind='box')
sns.catplot(data=df, x='Continent', y='2022 Population', kind='violin')
sns.catplot(data=df, x='Continent', y='2022 Population', kind='strip')
sns.catplot(data=df, x='Continent', y='2022 Population', kind='swarm')
sns.catplot(data=df, x='Continent', y='2022 Population', kind='point')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;matrix-amp-regression-charts-correlations-and-trends&quot;&gt;Matrix &amp;amp; Regression Charts — Correlations and Trends&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;corr()&lt;/code&gt; + &lt;code&gt;heatmap()&lt;/code&gt;&lt;/strong&gt; — the most common combination for exploring how numeric columns relate to each other:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;corr = df.select_dtypes(include='number').corr()
sns.heatmap(corr, annot=True, cmap='coolwarm')
plt.title(&quot;Correlation Matrix&quot;)
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The values range from -1.0 (perfect negative relationship) to 1.0 (perfect positive relationship). &lt;code&gt;annot=True&lt;/code&gt; displays the number inside each cell. &lt;code&gt;cmap='coolwarm'&lt;/code&gt; colors high values red and low values blue.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;clustermap()&lt;/code&gt;&lt;/strong&gt; — same as heatmap but automatically reorders rows and columns by similarity using clustering:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.clustermap(corr, annot=True, cmap='coolwarm')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;pairplot()&lt;/code&gt;&lt;/strong&gt; — creates scatter plots for every combination of numeric columns at once, with distribution plots along the diagonal. The fastest way to get a complete overview of your data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# All numeric columns
sns.pairplot(df.select_dtypes(include='number'))

# Colored by a category
sns.pairplot(df, hue='Continent')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;jointplot()&lt;/code&gt;&lt;/strong&gt; — shows a scatter plot of two columns, plus the distribution of each column on the margins:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.jointplot(data=df, x='Area (km2)', y='2022 Population')

# With a regression line
sns.jointplot(data=df, x='Area (km2)', y='2022 Population', kind='reg')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;lmplot()&lt;/code&gt;&lt;/strong&gt; — scatter plot with a regression line fitted through the data. Supports separate lines per group:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.lmplot(data=df, x='Area (km2)', y='2022 Population')

# Separate regression line per continent
sns.lmplot(data=df, x='Area (km2)', y='2022 Population', hue='Continent')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;regplot()&lt;/code&gt;&lt;/strong&gt; — the axes-level version of &lt;code&gt;lmplot()&lt;/code&gt;. Does the same thing but doesn't support grouping. Use when you want to embed a regression plot inside a larger figure with subplots:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.regplot(data=df, x='Area (km2)', y='2022 Population')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;residplot()&lt;/code&gt;&lt;/strong&gt; — shows the residuals of a regression: how far each actual point is from the predicted line. A flat, random scatter around zero means the regression is a good fit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.residplot(data=df, x='Area (km2)', y='2022 Population')
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;combining-seaborn-with-matplotlib&quot;&gt;Combining Seaborn with Matplotlib&lt;/h4&gt;
&lt;p&gt;Seaborn creates the chart. Matplotlib customizes it. Both work together in the same block — you never have to choose one or the other:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sns.boxplot(data=df, x='Continent', y='2022 Population', palette='pastel')

plt.title('Population Distribution by Continent')
plt.xlabel('Continent')
plt.ylabel('Population')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the standard workflow: Seaborn for the chart, Matplotlib for the title, labels, rotation, and layout.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;choosing-the-right-tool&quot;&gt;Choosing the Right Tool&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Use Matplotlib when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your data is in raw lists or NumPy arrays, not a DataFrame&lt;/li&gt;
&lt;li&gt;You need multiple subplots in a specific layout&lt;/li&gt;
&lt;li&gt;You want pixel-level control over every visual element&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Use Seaborn when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your data is in a DataFrame and ready to visualize&lt;/li&gt;
&lt;li&gt;You want a beautiful chart quickly with minimal code&lt;/li&gt;
&lt;li&gt;You're doing statistical analysis — distributions, correlations, regressions, group comparisons&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In most real projects, you'll use both: Seaborn to draw the chart, Matplotlib to label and polish it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;GitHub Repo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Hu8MA/Mastering-Pandas-Reference&quot;&gt;https://github.com/Hu8MA/Mastering-Pandas-Reference&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Matplotlib Documentation: &lt;a rel=&quot;nofollow&quot; href=&quot;https://matplotlib.org/&quot;&gt;https://matplotlib.org/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Seaborn Documentation: &lt;a rel=&quot;nofollow&quot; href=&quot;https://seaborn.pydata.org/&quot;&gt;https://seaborn.pydata.org/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pandas Visualization Guide: &lt;a rel=&quot;nofollow&quot; href=&quot;https://pandas.pydata.org/docs/user_guide/visualization.html&quot;&gt;https://pandas.pydata.org/docs/user_guide/visualization.html&lt;/a&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/13871/mastering-pandas-part-4-data-visualization-with-matplotlib-seaborn</guid>
<pubDate>Thu, 02 Apr 2026 19:32:22 +0000</pubDate>
</item>
<item>
<title>Build a Data Science Query Language in Python using Lark</title>
<link>https://coderlegion.com/13626/build-a-data-science-query-language-in-python-using-lark</link>
<description>&lt;h2 id=&quot;build-a-data-science-query-language-in-python-using-lark&quot;&gt;Build a Data Science Query Language in Python using Lark&lt;/h2&gt;
&lt;p&gt;What if you could write something like this:&lt;/p&gt;
&lt;p&gt;DATA [1, 2, 3, 4, 5]&lt;br&gt;
SUM&lt;br&gt;
MEAN&lt;br&gt;
STD&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
…and have it behave like a mini data science engine?

In this tutorial, we’ll build a **Domain-Specific Language (DSL)** for data analysis using:

- Python   
- Lark (parser library)   
- NumPy   

---

#  What Are We Building?

We are creating a **custom query language** that:

- Accepts a dataset
- Runs statistical commands
- Prints results

---

#  Step 1: Install Dependencies

```bash
pip install lark numpy
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-2-define-the-grammar&quot;&gt;Step 2: Define the Grammar&lt;/h2&gt;
&lt;p&gt;The grammar defines how our language looks.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from lark import Lark, Transformer
import numpy as np

grammar = &quot;&quot;&quot;
start: data command+

data: &quot;DATA&quot; list

command: &quot;SUM&quot; -&amp;gt; sum
       | &quot;MEAN&quot; -&amp;gt; mean
       | &quot;STD&quot; -&amp;gt; std
       | &quot;MAX&quot; -&amp;gt; max
       | &quot;MIN&quot; -&amp;gt; min

list: &quot;[&quot; NUMBER (&quot;,&quot; NUMBER)* &quot;]&quot;

%import common.NUMBER
%import common.WS
%ignore WS
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;explanation&quot;&gt;Explanation&lt;/h3&gt;
&lt;h4 id=&quot;start-data-command-&quot;&gt;&lt;code&gt;start: data command+&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Program must start with &lt;code&gt;DATA&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Followed by &lt;strong&gt;one or more commands&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;data-data-list&quot;&gt;&lt;code&gt;data: &quot;DATA&quot; list&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Defines dataset input&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DATA [1, 2, 3]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;commands&quot;&gt;Commands&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;SUM → sum
MEAN → mean
STD → std
MAX → max
MIN → min
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;These map text → function names&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-&amp;gt; sum&lt;/code&gt; means call &lt;code&gt;sum()&lt;/code&gt; in Transformer&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;list-rule&quot;&gt;List Rule&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;list: &quot;[&quot; NUMBER (&quot;,&quot; NUMBER)* &quot;]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Accepts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[1]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[1, 2, 3]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(, NUMBER)*&lt;/code&gt; means repeat&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;ignore-spaces&quot;&gt;Ignore Spaces&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;%ignore WS
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Allows flexible formatting&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-step-3-build-the-interpreter&quot;&gt;⚙️ Step 3: Build the Interpreter&lt;/h2&gt;
&lt;p&gt;Now we convert parsed text into execution.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class DLangInterpreter(Transformer):

    def data(self, items):
        self.data = np.array([float(x) for x in items[0]])
        return self.data
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;explanation-1&quot;&gt;Explanation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;items[0]&lt;/code&gt; → list of numbers&lt;/li&gt;
&lt;li&gt;Convert to NumPy array&lt;/li&gt;
&lt;li&gt;Store in &lt;code&gt;self.data&lt;/code&gt; for reuse&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-4-add-operations&quot;&gt;Step 4: Add Operations&lt;/h2&gt;
&lt;h4 id=&quot;sum&quot;&gt;SUM&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def sum(self, _):
    print(np.sum(self.data))
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;mean&quot;&gt;MEAN&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def mean(self, _):
    print(np.mean(self.data))
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;std&quot;&gt;STD&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def std(self, _):
    print(np.std(self.data))
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;max&quot;&gt;MAX&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def max(self, _):
    print(np.max(self.data))
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;min&quot;&gt;MIN&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def min(self, _):
    print(np.min(self.data))
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;explanation-2&quot;&gt;Explanation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Each function matches grammar rule&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_&lt;/code&gt; = unused input&lt;/li&gt;
&lt;li&gt;Uses NumPy for computation&lt;/li&gt;
&lt;li&gt;Prints result immediately&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-5-parse-list&quot;&gt;Step 5: Parse List&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def list(self, items):
    return items
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;explanation-3&quot;&gt;Explanation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Returns list of numbers&lt;/li&gt;
&lt;li&gt;Passed to &lt;code&gt;data()&lt;/code&gt; method&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-6-create-the-parser&quot;&gt;Step 6: Create the Parser&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;parser = Lark(grammar, parser=&quot;lalr&quot;, transformer=DLangInterpreter())
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;explanation-4&quot;&gt;Explanation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lalr&lt;/code&gt; → fast parsing algorithm&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transformer&lt;/code&gt; → auto-executes logic&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-7-read-input-file&quot;&gt;Step 7: Read Input File&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with open(&quot;example.dl&quot;) as f:
    code = f.read()

parser.parse(code)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;example-example-dl&quot;&gt;Example &lt;code&gt;example.dl&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;DATA [10, 20, 30, 40]
SUM
MEAN
MAX
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-output&quot;&gt;✅ Output&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;100
25.0
40
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-it-works-flow-&quot;&gt;How It Works (Flow)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Text Input
   ↓
Parser (Lark)
   ↓
Grammar Rules Match
   ↓
Transformer Methods Trigger
   ↓
NumPy Executes
   ↓
Output Printed
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-why-this-is-powerful&quot;&gt;✨ Why This Is Powerful&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;You built a &lt;strong&gt;mini programming language&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clean separation of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Syntax (grammar)&lt;/li&gt;
&lt;li&gt;Execution (Transformer)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Easily extensible&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;next-features-you-can-add&quot;&gt;Next Features You Can Add&lt;/h2&gt;
&lt;h4 id=&quot;1-filtering&quot;&gt;1. Filtering&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;FILTER &amp;gt; 10
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-sorting&quot;&gt;2. Sorting&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;SORT ASC
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-csv-support&quot;&gt;3. CSV Support&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;DATA file.csv
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;4-chaining&quot;&gt;4. Chaining&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;DATA [1,2,3,4]
FILTER &amp;gt; 2
MEAN
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thought&quot;&gt;Final Thought&lt;/h2&gt;
&lt;p&gt;This is how real systems like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SQL&lt;/li&gt;
&lt;li&gt;Pandas query engine&lt;/li&gt;
&lt;li&gt;Spark&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;…start at a basic level.&lt;/p&gt;
&lt;p&gt;You just built the foundation of a &lt;strong&gt;data query engine&lt;/strong&gt; &lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;if-you-liked-this&quot;&gt;If You Liked This&lt;/h2&gt;
&lt;p&gt;Drop a like ❤️&lt;br&gt;
Follow for more AI + Systems content&lt;br&gt;
And try extending this DSL yourself!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13626/build-a-data-science-query-language-in-python-using-lark</guid>
<pubDate>Sun, 29 Mar 2026 06:57:34 +0000</pubDate>
</item>
<item>
<title>Mastering Pandas — Part 3: Data Cleaning, Merging &amp; Joining</title>
<link>https://coderlegion.com/13151/mastering-pandas-part-3-data-cleaning-merging-joining</link>
<description>&lt;h3 id=&quot;real-data-is-never-clean&quot;&gt;Real Data Is Never Clean&lt;/h3&gt;
&lt;p&gt;In Part 2, you learned how to group and slice a DataFrame with precision. But in real projects, the data you receive rarely arrives ready to analyze. Column names have spaces. Ages are negative. Phone numbers mix letters with digits. Duplicate rows sneak in. And your data often lives across multiple files that need to be combined before you can do anything useful.&lt;/p&gt;
&lt;p&gt;This article covers the two skills that bridge raw data and real analysis:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Data Cleaning&lt;/strong&gt; — detecting and fixing everything wrong with your data before it corrupts your results&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Merging &amp;amp; Joining&lt;/strong&gt; — combining separate DataFrames into a single, unified dataset&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These topics flow naturally into each other. You clean each source first, then you combine them. That's the order every data analyst follows in practice — and it's the order we'll follow here.&lt;/p&gt;
&lt;p&gt;We'll use this sample dataset throughout the cleaning section:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pandas as pd
import numpy as np

data = {
    &quot;Name&quot;:             [&quot;Alice&quot;, &quot;bob&quot;, &quot;Carol&quot;, &quot;Alice&quot;, &quot;  dave  &quot;, &quot;Eve&quot;],
    &quot;Age&quot;:              [25, -5, 200, 25, 30, None],
    &quot;Phone&quot;:            [&quot;055-123-4567&quot;, None, &quot;55123x&quot;, &quot;055-123-4567&quot;, &quot;055-987-0000&quot;, &quot;055abc&quot;],
    &quot;Email&quot;:            [&quot;*Emails are not allowed*&quot;, &quot;*Emails are not allowed*&quot;, None, &quot;*Emails are not allowed*&quot;, &quot;*Emails are not allowed*&quot;, &quot;not-an-email&quot;],
    &quot;Paying_Customer&quot;:  [&quot;Y&quot;, &quot;N&quot;, &quot;Yes&quot;, &quot;Y&quot;, &quot;N&quot;, &quot;y&quot;],
    &quot;Score&quot;:            [85, 110, 72, 85, -10, 90],
}
df = pd.DataFrame(data)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-1-inspect-before-you-touch-anything&quot;&gt;Step 1 — Inspect Before You Touch Anything&lt;/h3&gt;
&lt;p&gt;The most important rule in data cleaning: &lt;strong&gt;look before you act&lt;/strong&gt;. Jumping straight into fixes without understanding the full picture leads to mistakes you won't catch until much later.&lt;/p&gt;
&lt;p&gt;These are the inspection tools you should run on every new dataset:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df.shape          # (rows, columns) — how big is this?
df.info()         # column names, data types, non-null counts
df.head()         # first 5 rows
df.tail()         # last 5 rows
df.describe()     # min, max, mean, std — spot impossible values here
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For missing values specifically:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df.isna().sum()           # count NaN per column
df.isnull().sum()         # same as isna() — both work
df[df[&quot;Phone&quot;].notna()]   # rows that actually have a phone number
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For duplicates and inconsistent categories:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df.duplicated()                         # True/False per row
df[df.duplicated()]                     # show only the duplicate rows
df[&quot;Paying_Customer&quot;].value_counts()    # reveals: Y, N, Yes, y — all meaning the same thing
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Always run &lt;code&gt;describe()&lt;/code&gt; on numeric columns&lt;/strong&gt; — the min and max alone will tell you immediately if something is wrong. A minimum age of -5 or a score of 110 out of 100 is a red flag you can't miss.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-fix-column-names-first&quot;&gt;Step 2 — Fix Column Names First&lt;/h3&gt;
&lt;p&gt;Messy column names make everything harder. Clean them before anything else, so every subsequent operation uses predictable, consistent names.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rename specific columns:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df.rename(columns={&quot;Old Name&quot;: &quot;New_Name&quot;, &quot;phone number&quot;: &quot;Phone&quot;})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Clean all column names at once — the standard one-liner:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df.columns = df.columns.str.strip().str.lower().str.replace(&quot; &quot;, &quot;_&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This strips surrounding spaces, converts to lowercase, and replaces spaces with underscores in a single chain. After this, &lt;code&gt;&quot;First Name&quot;&lt;/code&gt; becomes &lt;code&gt;&quot;first_name&quot;&lt;/code&gt;, &lt;code&gt;&quot;  Age &quot;&lt;/code&gt; becomes &lt;code&gt;&quot;age&quot;&lt;/code&gt;, and so on.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Before:              After:
&quot;First Name&quot;    →    &quot;first_name&quot;
&quot;  Age &quot;        →    &quot;age&quot;
&quot;Phone Number&quot;  →    &quot;phone_number&quot;
&quot;ZIP Code&quot;      →    &quot;zip_code&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-3-handle-missing-values&quot;&gt;Step 3 — Handle Missing Values&lt;/h3&gt;
&lt;p&gt;Missing values appear as &lt;code&gt;NaN&lt;/code&gt; in Pandas. Before deciding what to do with them, you need to understand &lt;em&gt;why&lt;/em&gt; they're missing — because the right fix depends on the cause.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fill missing values with a fixed value:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df[&quot;Phone&quot;].fillna(&quot;000-000-0000&quot;)    # placeholder for missing phone
df[&quot;Age&quot;].fillna(0)                   # fill numeric with 0
df[&quot;Name&quot;].fillna(&quot;Unknown&quot;)          # fill text with label
df[&quot;Score&quot;].fillna(df[&quot;Score&quot;].mean()) # fill with column average
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Fill missing numeric values by estimating from neighbors:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df[&quot;Sales&quot;] = df[&quot;Sales&quot;].interpolate()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;interpolate()&lt;/code&gt; fills NaN by calculating the midpoint between the values above and below it. This is ideal for time series or sensor data where values change gradually — it would be wrong to use for random datasets.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Before:     After interpolate():
100         100
NaN    →    150   ← estimated midpoint
200         200
NaN    →    225   ← estimated midpoint
250         250
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Remove rows with missing values:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df.dropna()                            # drop any row with at least one NaN
df.dropna(subset=[&quot;Phone&quot;])            # drop only if Phone is NaN
df.dropna(thresh=3)                    # drop rows that have fewer than 3 non-NaN values
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;inplace&lt;/code&gt; rule:&lt;/strong&gt; use either &lt;code&gt;inplace=True&lt;/code&gt; or variable assignment — never both. They do the same thing, and combining them produces a bug where &lt;code&gt;df&lt;/code&gt; becomes &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df.dropna(subset=[&quot;Phone&quot;], inplace=True)   # ✅ modifies df directly
df = df.dropna(subset=[&quot;Phone&quot;])            # ✅ returns new df
df = df.dropna(subset=[&quot;Phone&quot;], inplace=True)  # ❌ df becomes None
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-4-remove-duplicates&quot;&gt;Step 4 — Remove Duplicates&lt;/h3&gt;
&lt;p&gt;Duplicate rows silently inflate counts, averages, and totals. Always check for them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df.drop_duplicates()                         # remove fully identical rows
df.drop_duplicates(subset=[&quot;Name&quot;])          # remove rows with duplicate Name only
df.drop_duplicates(subset=[&quot;Name&quot;, &quot;Email&quot;]) # remove rows where both columns match
df.drop_duplicates(keep=&quot;last&quot;)              # keep the last occurrence instead of the first
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Before:                        After drop_duplicates():
Name   Email                   Name   Email
Alice  *Emails are not allowed*          Alice  *Emails are not allowed*
Bob    *Emails are not allowed*      →     Bob    *Emails are not allowed*
Alice  *Emails are not allowed* ← dup    Carol  *Emails are not allowed*
Carol  *Emails are not allowed*
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-5-fix-data-types&quot;&gt;Step 5 — Fix Data Types&lt;/h3&gt;
&lt;p&gt;Pandas sometimes reads numeric columns as strings, or dates as plain text. Operations on wrong types either crash or silently produce wrong results.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Change column type directly:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df[&quot;Zip_Code&quot;] = df[&quot;Zip_Code&quot;].astype(str)     # treat as text, not a number
df[&quot;Age&quot;] = df[&quot;Age&quot;].astype(int)               # convert to integer
df[&quot;Price&quot;] = df[&quot;Price&quot;].astype(float)         # convert to decimal
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Handle messy numbers that contain symbols or text:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df[&quot;Price&quot;] = pd.to_numeric(df[&quot;Price&quot;], errors=&quot;coerce&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;errors=&quot;coerce&quot;&lt;/code&gt; converts anything that can't be parsed into &lt;code&gt;NaN&lt;/code&gt; instead of crashing. This is the safe way to clean columns like &lt;code&gt;&quot;$1,200&quot;&lt;/code&gt; or &lt;code&gt;&quot;N/A&quot;&lt;/code&gt; that can't be directly cast with &lt;code&gt;astype()&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Before:           After pd.to_numeric(..., errors=&quot;coerce&quot;):
&quot;1200&quot;    →       1200.0
&quot;$850&quot;    →       NaN     ← couldn't parse
&quot;700.5&quot;   →       700.5
&quot;N/A&quot;     →       NaN     ← couldn't parse
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Convert to datetime:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df[&quot;Date&quot;] = pd.to_datetime(df[&quot;Date&quot;])
df[&quot;Date&quot;] = pd.to_datetime(df[&quot;Date&quot;], format=&quot;%d/%m/%Y&quot;)  # specify format if needed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once a column is proper datetime, you unlock sorting by date, filtering by year/month, calculating time differences, and much more.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-6-clean-strings&quot;&gt;Step 6 — Clean Strings&lt;/h3&gt;
&lt;p&gt;String columns are almost always the messiest part of a real dataset. Pandas provides a full &lt;code&gt;.str&lt;/code&gt; accessor with everything you need.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Strip unwanted characters from edges:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df[&quot;Name&quot;] = df[&quot;Name&quot;].str.strip()           # remove spaces from both sides
df[&quot;Name&quot;] = df[&quot;Name&quot;].str.strip(&quot; ./-&quot;)     # remove spaces, dots, slashes, dashes
df[&quot;Name&quot;] = df[&quot;Name&quot;].str.lstrip()          # left side only
df[&quot;Name&quot;] = df[&quot;Name&quot;].str.rstrip()          # right side only
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Replace characters anywhere in the string:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df[&quot;Phone&quot;] = df[&quot;Phone&quot;].str.replace(r&quot;[a-zA-Z]&quot;, &quot;&quot;, regex=True)   # remove all letters
df[&quot;Phone&quot;] = df[&quot;Phone&quot;].str.replace(r&quot;\D&quot;, &quot;&quot;, regex=True)          # keep digits only
df[&quot;Name&quot;]  = df[&quot;Name&quot;].str.replace(r&quot;[./]&quot;, &quot;&quot;, regex=True)         # remove dots and slashes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Standardize text case:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df[&quot;Name&quot;] = df[&quot;Name&quot;].str.lower()     # all lowercase → &quot;alice&quot;
df[&quot;Name&quot;] = df[&quot;Name&quot;].str.upper()     # all uppercase → &quot;ALICE&quot;
df[&quot;Name&quot;] = df[&quot;Name&quot;].str.title()     # title case    → &quot;Alice&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Check text content:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df[df[&quot;Email&quot;].str.contains(&quot;@&quot;)]           # filter valid-looking emails
df[df[&quot;Phone&quot;].str.len() != 12]             # find rows where phone length is wrong
df[df[&quot;Name&quot;].str.startswith(&quot;A&quot;)]          # names starting with A
df[df[&quot;Name&quot;].str.endswith(&quot;e&quot;)]            # names ending with e
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Extract a pattern using regex:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df[&quot;Zip&quot;] = df[&quot;Address&quot;].str.extract(r&quot;(\d{5})&quot;)        # pull 5-digit zip code
df[&quot;Area_Code&quot;] = df[&quot;Phone&quot;].str.extract(r&quot;^(\d{3})&quot;)   # pull first 3 digits
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Split one column into multiple columns:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df[[&quot;Street&quot;, &quot;State&quot;, &quot;Zip&quot;]] = (
    df[&quot;Address&quot;]
    .str.split(&quot;,&quot;, n=2, expand=True)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;n=2&lt;/code&gt; limits the split to 2 times, producing exactly 3 parts regardless of how many commas the string contains. &lt;code&gt;expand=True&lt;/code&gt; spreads those parts into separate columns.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Before:                              After:
&quot;123 Main St, Texas, 75001&quot;   →   Street=&quot;123 Main St&quot;  State=&quot;Texas&quot;  Zip=&quot;75001&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-7-replace-and-standardize-values&quot;&gt;Step 7 — Replace and Standardize Values&lt;/h3&gt;
&lt;p&gt;Raw data often has the same value written multiple ways: &lt;code&gt;&quot;Y&quot;&lt;/code&gt;, &lt;code&gt;&quot;Yes&quot;&lt;/code&gt;, &lt;code&gt;&quot;yes&quot;&lt;/code&gt;, &lt;code&gt;&quot;y&quot;&lt;/code&gt; — all meaning the same thing. Standardize them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Replace whole cell values using a dictionary:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df[&quot;Status&quot;] = df[&quot;Status&quot;].replace({&quot;Y&quot;: &quot;Yes&quot;, &quot;N&quot;: &quot;No&quot;, &quot;y&quot;: &quot;Yes&quot;})

# Apply to multiple columns at once
df[[&quot;Paying_Customer&quot;, &quot;Do_Not_Contact&quot;]] = (
    df[[&quot;Paying_Customer&quot;, &quot;Do_Not_Contact&quot;]]
    .replace({&quot;Y&quot;: &quot;Yes&quot;, &quot;N&quot;: &quot;No&quot;, &quot;y&quot;: &quot;Yes&quot;, &quot;n&quot;: &quot;No&quot;})
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Overwrite invalid values using &lt;code&gt;loc[]&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df.loc[df[&quot;Age&quot;] &amp;lt; 0,   &quot;Age&quot;] = None     # negative age → invalid
df.loc[df[&quot;Age&quot;] &amp;gt; 120, &quot;Age&quot;] = None     # age over 120 → invalid
df.loc[df[&quot;Price&quot;] &amp;lt; 0, &quot;Price&quot;] = None   # negative price → invalid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Keep valid values, replace everything else with NaN:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;df[&quot;Salary&quot;] = df[&quot;Salary&quot;].where(df[&quot;Salary&quot;] &amp;gt; 0)
# Rows where Salary &amp;lt;= 0 become NaN; the rest are untouched
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;now-combine-merging-amp-joining-dataframes&quot;&gt;Now Combine — Merging &amp;amp; Joining DataFrames&lt;/h3&gt;
&lt;p&gt;Once each source is clean, the next challenge is combining them. In the real world, data almost never lives in a single file. You might have orders in one table, customers in another, and products in a third. Merging is how you bring those pieces together.&lt;/p&gt;
&lt;p&gt;Pandas provides three main tools for this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Tool &lt;/th&gt;&lt;th&gt;What it does&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;merge()&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Joins two DataFrames by matching on a shared column&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;concat()&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Stacks DataFrames vertically or side by side&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;join()&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Joins on the index instead of a column&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We'll use these two DataFrames for all the examples:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;customers = pd.DataFrame({
    &quot;ID&quot;:        [1, 2, 3, 4],
    &quot;Name&quot;:      [&quot;Alice&quot;, &quot;Bob&quot;, &quot;Carol&quot;, &quot;Dave&quot;],
    &quot;Country&quot;:   [&quot;USA&quot;, &quot;UK&quot;, &quot;India&quot;, &quot;USA&quot;],
})

orders = pd.DataFrame({
    &quot;ID&quot;:        [1, 2, 3, 5],
    &quot;Product&quot;:   [&quot;Laptop&quot;, &quot;Phone&quot;, &quot;Tablet&quot;, &quot;Monitor&quot;],
    &quot;Price&quot;:     [1200, 800, 600, 400],
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that &lt;code&gt;ID=4&lt;/code&gt; (Dave) exists in &lt;code&gt;customers&lt;/code&gt; but not in &lt;code&gt;orders&lt;/code&gt;, and &lt;code&gt;ID=5&lt;/code&gt; exists in &lt;code&gt;orders&lt;/code&gt; but not in &lt;code&gt;customers&lt;/code&gt;. This is exactly the kind of mismatch you encounter in practice — and why the &lt;code&gt;how=&lt;/code&gt; parameter matters so much.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-merge-the-main-tool&quot;&gt;1️⃣ &lt;code&gt;merge()&lt;/code&gt; — The Main Tool&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;merge()&lt;/code&gt; works like a SQL JOIN. It aligns two DataFrames by finding rows where a shared column has the same value.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Basic syntax:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;customers.merge(orders, how=&quot;inner&quot;, on=&quot;ID&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-how-parameter-all-four-types&quot;&gt;The &lt;code&gt;how=&lt;/code&gt; Parameter — All Four Types&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# INNER → only rows that match in both DataFrames (default)
customers.merge(orders, how=&quot;inner&quot;, on=&quot;ID&quot;)
# Result: IDs 1, 2, 3 only — Dave (4) and Monitor (5) are excluded

# LEFT → all rows from the left + matched rows from the right
customers.merge(orders, how=&quot;left&quot;, on=&quot;ID&quot;)
# Result: all 4 customers — Dave gets NaN for Product and Price

# RIGHT → all rows from the right + matched rows from the left
customers.merge(orders, how=&quot;right&quot;, on=&quot;ID&quot;)
# Result: all 4 orders — Monitor (ID=5) gets NaN for Name and Country

# OUTER → all rows from both, NaN where no match
customers.merge(orders, how=&quot;outer&quot;, on=&quot;ID&quot;)
# Result: all 5 unique IDs — gaps filled with NaN on both sides
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;code&gt;how=&lt;/code&gt; &lt;/th&gt;&lt;th&gt;Keeps from Left &lt;/th&gt;&lt;th&gt;Keeps from Right &lt;/th&gt;&lt;th&gt;NaN Possible&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;inner&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Matched only &lt;/td&gt;&lt;td&gt;Matched only &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;left&lt;/code&gt; &lt;/td&gt;&lt;td&gt;All rows &lt;/td&gt;&lt;td&gt;Matched only &lt;/td&gt;&lt;td&gt;Right side&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;right&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Matched only &lt;/td&gt;&lt;td&gt;All rows &lt;/td&gt;&lt;td&gt;Left side&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;outer&lt;/code&gt; &lt;/td&gt;&lt;td&gt;All rows &lt;/td&gt;&lt;td&gt;All rows &lt;/td&gt;&lt;td&gt;Both sides&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;CROSS merge — every row with every row:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;customers.merge(orders, how=&quot;cross&quot;)
# Produces 4 × 4 = 16 rows — every customer paired with every order
# No on= parameter — it will raise an error if you include it
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-on-parameter-what-to-match-on&quot;&gt;The &lt;code&gt;on=&lt;/code&gt; Parameter — What to Match On&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Match on a single column
customers.merge(orders, how=&quot;inner&quot;, on=&quot;ID&quot;)

# Match on multiple columns — both must match simultaneously
customers.merge(orders, how=&quot;inner&quot;, on=[&quot;ID&quot;, &quot;Country&quot;])

# Auto-detect — Pandas finds all shared column names automatically
customers.merge(orders)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Be careful with auto-detect.&lt;/strong&gt; If both DataFrames happen to share a column that you don't intend to join on (like a generic &lt;code&gt;&quot;Name&quot;&lt;/code&gt; column), Pandas will include it in the match condition and produce unexpected results. Explicit is safer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h4 id=&quot;handling-shared-column-names-x-and-y&quot;&gt;Handling Shared Column Names — &lt;code&gt;_x&lt;/code&gt; and &lt;code&gt;_y&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;If both DataFrames have a column with the same name, but you only join on &lt;code&gt;ID&lt;/code&gt;, Pandas keeps both copies and renames them automatically:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Both DataFrames have a &quot;Name&quot; column, but you only merge on &quot;ID&quot;
customers.merge(orders, how=&quot;inner&quot;, on=&quot;ID&quot;)
# Result: Name_x (from customers), Name_y (from orders)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three ways to fix this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Option 1 — merge on all shared columns so no duplicates appear
customers.merge(orders, how=&quot;inner&quot;, on=[&quot;ID&quot;, &quot;Name&quot;])

# Option 2 — use custom suffixes instead of _x and _y
customers.merge(orders, how=&quot;inner&quot;, on=&quot;ID&quot;, suffixes=(&quot;_customer&quot;, &quot;_order&quot;))

# Option 3 — drop the shared column from one DataFrame before merging
orders_clean = orders.drop(columns=[&quot;Name&quot;])
customers.merge(orders_clean, how=&quot;inner&quot;, on=&quot;ID&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;other-useful-parameters&quot;&gt;Other Useful Parameters&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# left_on / right_on — when the key column has different names in each DataFrame
customers.merge(orders, how=&quot;inner&quot;, left_on=&quot;CustomerID&quot;, right_on=&quot;OrderID&quot;)

# indicator — adds a column showing where each row came from
customers.merge(orders, how=&quot;outer&quot;, on=&quot;ID&quot;, indicator=True)
# _merge column values: &quot;left_only&quot;, &quot;right_only&quot;, &quot;both&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;indicator=True&lt;/code&gt; parameter is especially useful for auditing: after a merge, you can immediately see which rows matched, which came only from the left, and which came only from the right.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-concat-stacking-dataframes&quot;&gt;2️⃣ &lt;code&gt;concat()&lt;/code&gt; — Stacking DataFrames&lt;/h3&gt;
&lt;p&gt;While &lt;code&gt;merge()&lt;/code&gt; combines DataFrames horizontally by matching values, &lt;code&gt;concat()&lt;/code&gt; simply stacks them — either rows on top of rows, or columns side by side.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;pd.concat([df1, df2])        # stack vertically (default)
pd.concat([df1, df2], axis=1)  # stack horizontally
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;axis-parameter&quot;&gt;&lt;code&gt;axis=&lt;/code&gt; Parameter&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# axis=0 → stack rows vertically (append one DataFrame below another)
monthly_data = pd.concat([january, february, march], axis=0)

# axis=1 → stack columns side by side
combined = pd.concat([names_df, scores_df], axis=1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;join-parameter&quot;&gt;&lt;code&gt;join=&lt;/code&gt; Parameter&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# outer (default) → keep all columns, NaN where a column doesn't exist in one source
pd.concat([df1, df2], join=&quot;outer&quot;)

# inner → keep only columns that exist in all DataFrames
pd.concat([df1, df2], join=&quot;inner&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;ignore-index-parameter&quot;&gt;&lt;code&gt;ignore_index=&lt;/code&gt; Parameter&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Without ignore_index — original indices are preserved, which can repeat
pd.concat([df1, df2])
# Index: 0, 1, 2, 0, 1, 2  ← 0, 1, 2 repeats

# With ignore_index=True — fresh sequential index
pd.concat([df1, df2], ignore_index=True)
# Index: 0, 1, 2, 3, 4, 5  ← clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Always use &lt;code&gt;ignore_index=True&lt;/code&gt; after stacking unless you specifically need the original indices.&lt;/p&gt;
&lt;h4 id=&quot;keys-parameter&quot;&gt;&lt;code&gt;keys=&lt;/code&gt; Parameter&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Label each source so you can identify where each row came from
combined = pd.concat([df1, df2], keys=[&quot;source_a&quot;, &quot;source_b&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a MultiIndex where the first level is your label and the second is the original row index — useful when you need to trace data back to its source after combining.&lt;/p&gt;
&lt;h4 id=&quot;verify-integrity-parameter&quot;&gt;&lt;code&gt;verify_integrity=&lt;/code&gt; Parameter&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Raises an error if any duplicate index values are found after concatenation
pd.concat([df1, df2], verify_integrity=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;merge-vs-concat-when-to-use-which&quot;&gt;&lt;code&gt;merge()&lt;/code&gt; vs &lt;code&gt;concat()&lt;/code&gt; — When to Use Which&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;&lt;code&gt;merge()&lt;/code&gt; &lt;/th&gt;&lt;th&gt;&lt;code&gt;concat()&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Direction &lt;/td&gt;&lt;td&gt;Horizontal (by matching) &lt;/td&gt;&lt;td&gt;Vertical or horizontal (by stacking)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Needs a shared key column &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Best for &lt;/td&gt;&lt;td&gt;Joining related tables &lt;/td&gt;&lt;td&gt;Combining same-structure data&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Equivalent to &lt;/td&gt;&lt;td&gt;SQL JOIN &lt;/td&gt;&lt;td&gt;SQL UNION&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A simple rule: if you're &lt;strong&gt;combining columns from different tables&lt;/strong&gt;, use &lt;code&gt;merge()&lt;/code&gt;. If you're &lt;strong&gt;stacking more rows of the same structure&lt;/strong&gt;, use &lt;code&gt;concat()&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-join-index-based-joining&quot;&gt;3️⃣ &lt;code&gt;join()&lt;/code&gt; — Index-Based Joining&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;join()&lt;/code&gt; works like &lt;code&gt;merge()&lt;/code&gt;, but it joins on the &lt;strong&gt;index&lt;/strong&gt; instead of a regular column. You need to set the index first.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;customers_indexed = customers.set_index(&quot;ID&quot;)
orders_indexed    = orders.set_index(&quot;ID&quot;)

customers_indexed.join(orders_indexed, how=&quot;inner&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;how=&lt;/code&gt; options are the same as &lt;code&gt;merge()&lt;/code&gt;: &lt;code&gt;inner&lt;/code&gt;, &lt;code&gt;left&lt;/code&gt;, &lt;code&gt;right&lt;/code&gt;, &lt;code&gt;outer&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;why-merge-is-almost-always-better&quot;&gt;Why &lt;code&gt;merge()&lt;/code&gt; Is Almost Always Better&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;&lt;code&gt;merge()&lt;/code&gt; &lt;/th&gt;&lt;th&gt;&lt;code&gt;join()&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Join on any column &lt;/td&gt;&lt;td&gt;✅ Yes &lt;/td&gt;&lt;td&gt;❌ No — index only&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Join on index &lt;/td&gt;&lt;td&gt;✅ Yes &lt;/td&gt;&lt;td&gt;✅ Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Requires &lt;code&gt;set_index()&lt;/code&gt; first &lt;/td&gt;&lt;td&gt;❌ No &lt;/td&gt;&lt;td&gt;✅ Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Simpler syntax &lt;/td&gt;&lt;td&gt;✅ Yes &lt;/td&gt;&lt;td&gt;⚠️ Extra steps&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;-gt-use-join-only-when-your-data-is-already-indexed-for-example-after-a-groupby-that-produced-a-meaningful-index-otherwise-merge-is-cleaner-and-more-flexible-&quot;&gt;&amp;gt; Use &lt;code&gt;join()&lt;/code&gt; only when your data is already indexed — for example, after a &lt;code&gt;groupby()&lt;/code&gt; that produced a meaningful index. Otherwise, &lt;code&gt;merge()&lt;/code&gt; is cleaner and more flexible.&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;This is Part 3 of the Pandas for Data Science series. Next up: Part 4 — Data Visualization with &lt;strong&gt;Matplotlib &amp;amp; Seaborn&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To access the full article, please visit the link below or directly access the repository via the links below.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Refrences&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Repo : &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Hu8MA/Mastering-Pandas-Reference&quot;&gt;https://github.com/Hu8MA/Mastering-Pandas-Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Website : &lt;a rel=&quot;nofollow&quot; href=&quot;https://pandas.pydata.org/&quot;&gt;https://pandas.pydata.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub Library : &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/pandas-dev/pandas&quot;&gt;https://github.com/pandas-dev/pandas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Course : &lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/Mdq1WWSdUtw&quot;&gt;https://youtu.be/Mdq1WWSdUtw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Full article : &lt;a rel=&quot;nofollow&quot; href=&quot;https://hussein16mahdi.medium.com/mastering-pandas-part-3-data-cleaning-merging-joining-817a070147ca&quot;&gt;https://hussein16mahdi.medium.com/mastering-pandas-part-3-data-cleaning-merging-joining-817a070147ca&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/13151/mastering-pandas-part-3-data-cleaning-merging-joining</guid>
<pubDate>Wed, 18 Mar 2026 23:24:55 +0000</pubDate>
</item>
<item>
<title>Lecture 3 — The Art of Collecting Data</title>
<link>https://coderlegion.com/12488/lecture-3-the-art-of-collecting-data</link>
<description>&lt;h2 id=&quot;lecture-3-the-art-of-collecting-data&quot;&gt;Lecture 3 — The Art of Collecting Data&lt;/h2&gt;
&lt;p&gt;By &lt;strong&gt;Vyoma Youth Society&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this lecture, we will discuss &lt;strong&gt;how data is collected&lt;/strong&gt; and perform a small practical example using Python.&lt;/p&gt;
&lt;p&gt;In the previous lectures, we discussed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what data really is&lt;/li&gt;
&lt;li&gt;why uncertainty exists in data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But before moving deeper into statistics and machine learning, we must understand something very basic:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Where does data come from?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Data does not appear magically.&lt;br&gt;
Collecting data is a &lt;strong&gt;skill and a process&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;So the first question we should ask is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Where can we collect data from?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Before continuing, pause for a moment and write down a few places where you think data can be collected.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;collection-of-data&quot;&gt;Collection of Data&lt;/h3&gt;
&lt;p&gt;Before we start working with statistics or building models, we must first have data to work with.&lt;/p&gt;
&lt;p&gt;Data collection is the &lt;strong&gt;foundation of data science&lt;/strong&gt;.&lt;br&gt;
Without data, analysis is impossible.&lt;/p&gt;
&lt;p&gt;When we think about collecting data, there are generally two main types of sources.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;types-of-data-sources&quot;&gt;Types of Data Sources&lt;/h3&gt;
&lt;h4 id=&quot;1-primary-sources&quot;&gt;1️⃣ Primary Sources&lt;/h4&gt;
&lt;p&gt;Primary data is the data that &lt;strong&gt;you collect yourself&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This usually involves first-hand methods such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;surveys&lt;/li&gt;
&lt;li&gt;interviews&lt;/li&gt;
&lt;li&gt;experiments&lt;/li&gt;
&lt;li&gt;observations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In primary data collection, you design the method and gather the data directly.&lt;/p&gt;
&lt;p&gt;Example:&lt;br&gt;
If you conduct a survey asking students about their study hours, the data you collect is &lt;strong&gt;primary data&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-secondary-sources&quot;&gt;2️⃣ Secondary Sources&lt;/h4&gt;
&lt;p&gt;Secondary data is data that has already been collected by someone else.&lt;/p&gt;
&lt;p&gt;Instead of collecting it yourself, you &lt;strong&gt;use existing datasets&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;public datasets&lt;/li&gt;
&lt;li&gt;research papers&lt;/li&gt;
&lt;li&gt;government statistics&lt;/li&gt;
&lt;li&gt;online databases&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using secondary data is very common in data science because many organizations publish large datasets.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;For now, we will only introduce these concepts.&lt;br&gt;
Later in this course, we will have a &lt;strong&gt;full module dedicated to data collection&lt;/strong&gt;, where we will study these methods in much greater detail.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;practical-section&quot;&gt;Practical Section&lt;/h2&gt;
&lt;p&gt;In the last two lectures we focused mainly on theory.&lt;br&gt;
So today we will do a &lt;strong&gt;small practical exercise&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Our goal is simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Compare the &lt;strong&gt;manual calculation of the mean&lt;/strong&gt; with the &lt;strong&gt;mean calculated by a Python library&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This helps us understand that libraries are simply &lt;strong&gt;implementations of mathematical formulas&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;python-practical&quot;&gt;Python Practical&lt;/h3&gt;
&lt;p&gt;First, create a Python file and write the following code.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np

a = np.array([i for i in range(10)])

# 1. Manual calculation
manual_mean = sum(a) / len(a)

# 2. Using NumPy
numpy_mean = np.mean(a)

print(f&quot;Manual mean: {manual_mean}&quot;)
print(f&quot;Numpy mean: {numpy_mean}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;understanding-what-happened&quot;&gt;Understanding What Happened&lt;/h3&gt;
&lt;p&gt;Before understanding the code, we should understand what &lt;strong&gt;mean&lt;/strong&gt; really is.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;mean&lt;/strong&gt; is simply the &lt;strong&gt;average value&lt;/strong&gt; of a dataset.&lt;/p&gt;
&lt;p&gt;The formula is:&lt;/p&gt;
&lt;p&gt;[
Mean = \frac{\text{Sum of all values}}{\text{Number of values}}
]&lt;/p&gt;
&lt;p&gt;In the program:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sum(a)&lt;/code&gt; adds all numbers in the dataset&lt;/li&gt;
&lt;li&gt;&lt;code&gt;len(a)&lt;/code&gt; gives the number of elements&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sum(a) / len(a)&lt;/code&gt; calculates the mean manually&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then we use the NumPy function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;np.mean(a)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function performs the same calculation internally.&lt;/p&gt;
&lt;p&gt;When you run the code, both methods give the same result:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4.5&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This shows that library functions are simply &lt;strong&gt;automated versions of mathematical formulas&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-this-practical-teaches-us&quot;&gt;What This Practical Teaches Us&lt;/h3&gt;
&lt;p&gt;1️⃣ Data must first be collected before analysis.&lt;br&gt;
2️⃣ Statistics is built on simple mathematical ideas.&lt;br&gt;
3️⃣ Programming libraries automate these calculations.&lt;/p&gt;
&lt;p&gt;Understanding the &lt;strong&gt;mathematics behind the libraries&lt;/strong&gt; is an important goal of this initiative.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;our-open-source-repository--&quot;&gt;Our open source repository :-&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/psjdeveloper/vyomadatascience&quot;&gt;https://github.com/psjdeveloper/vyomadatascience&lt;/a&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/12488/lecture-3-the-art-of-collecting-data</guid>
<pubDate>Mon, 09 Mar 2026 11:09:38 +0000</pubDate>
</item>
<item>
<title>Forecast Kebutuhan Bahan &amp; Produksi Konveksi dengan Python (Praktis + Template)</title>
<link>https://coderlegion.com/12135/forecast-kebutuhan-bahan-produksi-konveksi-dengan-python-praktis-template</link>
<description>&lt;p&gt;Di konveksi, “ramai order” bukan selalu kabar baik—kalau stok kain meleset, jadwal jahit jadi chaos, dan vendor bahan mulai susah diajak senyum. Industri manufaktur (termasuk apparel) makin banyak mengadopsi &lt;em&gt;smart production planning&lt;/em&gt; untuk menekan biaya dan menaikkan akurasi keputusan, seperti dibahas dalam artikel tentang &lt;strong&gt;perencanaan produksi cerdas&lt;/strong&gt; di &lt;a rel=&quot;nofollow&quot; href=&quot;https://fmipa.unpad.ac.id/smart-production-planning-boosts-clothing-industry-profits-and-supports-sustainable-growth/&quot;&gt;FMIPA Unpad&lt;/a&gt;. Kalau Anda pernah “kehabisan kain 2 roll” atau “kebanyakan stok warna yang salah”, artikel ini akan jadi &lt;em&gt;playbook&lt;/em&gt; yang bisa dipraktikkan—dan kita akan menutup paragraf ini dengan satu frasa yang akan jadi kompas SEO sekaligus kompas kerja: &lt;strong&gt;forecast kebutuhan bahan python&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Dari sisi ilmiah, praktik peramalan permintaan (demand forecasting) yang memadukan data historis dan fitur kontekstual sudah jadi fondasi kuat di supply chain modern; salah satu rujukan riset yang relevan bisa Anda jadikan landasan adalah artikel di &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.sciencedirect.com/science/article/abs/pii/S0169207023000134&quot;&gt;ScienceDirect tentang demand forecasting&lt;/a&gt;. Kita mengangkat tema ini karena banyak developer (dan founder) membangun sistem order tapi lupa “otak belakangnya”: perencanaan bahan, &lt;em&gt;capacity planning&lt;/em&gt;, dan jadwal produksi yang data-driven—padahal di lapangan, itu yang menentukan order selesai tepat waktu.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;“Kalau data order sudah rapi, stok bahan seharusnya bisa diprediksi. Kalau stok bisa diprediksi, jadwal produksi bisa diatur. Kalau jadwal teratur, reputasi naik.”&lt;/strong&gt;&lt;br&gt;
Ringkasnya: kita tidak sedang membahas “sekadar analitik”, tapi cara membuat operasional konveksi terasa seperti &lt;em&gt;product engineering&lt;/em&gt;—ada metrik, ada logika, ada iterasi.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-kenapa-konveksi-perlu-forecast-bukan-tebak-tebakan&quot;&gt;1. Kenapa Konveksi Perlu Forecast, Bukan Tebak-Tebakan&lt;/h3&gt;
&lt;p&gt;Di banyak bisnis konveksi, pengambilan keputusan bahan masih mengandalkan intuisi: “bulan lalu laku, bulan ini pasti laku.” Masalahnya, pola permintaan seragam kerja, jersey event, sampai pakaian muslim sering dipengaruhi kalender (lebaran, wisuda, agenda HR, event olahraga), tren desain, bahkan &lt;em&gt;lead time&lt;/em&gt; vendor. Di sinilah &lt;strong&gt;forecast kebutuhan bahan python&lt;/strong&gt; jadi alat bantu: bukan untuk meramal masa depan secara mistis, tapi untuk mengurangi &lt;em&gt;variance&lt;/em&gt; keputusan.&lt;/p&gt;
&lt;h4 id=&quot;apa-yang-biasanya-bocor-di-alur-produksi&quot;&gt;Apa yang Biasanya “Bocor” di Alur Produksi&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Overstock&lt;/strong&gt;: kain numpuk, cashflow ketahan.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stockout&lt;/strong&gt;: produksi berhenti, deadline kacau.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mismatch spesifikasi&lt;/strong&gt;: warna/gramasi salah karena reorder buru-buru.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bottleneck jahit/finishing&lt;/strong&gt;: bahan ada, kapasitas tidak siap.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;sinyal-data-yang-sering-diabaikan&quot;&gt;Sinyal Data yang Sering Diabaikan&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Tanggal &lt;em&gt;PO&lt;/em&gt; vs tanggal &lt;em&gt;delivery&lt;/em&gt; (indikator lead time real).&lt;/li&gt;
&lt;li&gt;Ukuran dominan (S/M/L/XL) per klien/per industri.&lt;/li&gt;
&lt;li&gt;Rasio reject/QC (pengaruh ke kebutuhan kain ekstra).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-data-minimal-yang-anda-butuhkan-dan-cara-menyusunnya-&quot;&gt;2. Data Minimal yang Anda Butuhkan (Dan Cara Menyusunnya)&lt;/h3&gt;
&lt;p&gt;Sebelum ngomong model, rapikan dulu datanya. Kabar baiknya: Anda tidak butuh data warehouse mewah. Cukup satu tabel order yang konsisten.&lt;/p&gt;
&lt;h4 id=&quot;struktur-tabel-data-order&quot;&gt;Struktur Tabel Data Order&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Kolom         &lt;/th&gt;&lt;th&gt;Contoh          &lt;/th&gt;&lt;th&gt;Kenapa penting         &lt;/th&gt;&lt;th&gt;Catatan praktis&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;order_id      &lt;/td&gt;&lt;td&gt;ORD-2026-0021   &lt;/td&gt;&lt;td&gt;identitas transaksi    &lt;/td&gt;&lt;td&gt;unik dan konsisten&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;tanggal_order &lt;/td&gt;&lt;td&gt;2026-02-10      &lt;/td&gt;&lt;td&gt;time series            &lt;/td&gt;&lt;td&gt;gunakan ISO date&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;kategori      &lt;/td&gt;&lt;td&gt;seragam kerja   &lt;/td&gt;&lt;td&gt;segmentasi permintaan  &lt;/td&gt;&lt;td&gt;bisa: seragam/jersey/muslim/merch&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;sku/desain    &lt;/td&gt;&lt;td&gt;SPV-KIIC-01     &lt;/td&gt;&lt;td&gt;pola demand per desain &lt;/td&gt;&lt;td&gt;opsional tapi bagus&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;qty           &lt;/td&gt;&lt;td&gt;120             &lt;/td&gt;&lt;td&gt;target produksi        &lt;/td&gt;&lt;td&gt;integer&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ukuran_mix    &lt;/td&gt;&lt;td&gt;M:60,L:40,XL:20 &lt;/td&gt;&lt;td&gt;estimasi konsumsi kain &lt;/td&gt;&lt;td&gt;bisa JSON&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;bahan         &lt;/td&gt;&lt;td&gt;oxford 200gsm   &lt;/td&gt;&lt;td&gt;konsumsi &amp;amp; substitusi  &lt;/td&gt;&lt;td&gt;normalisasi nama&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;lebar_kain_cm &lt;/td&gt;&lt;td&gt;150             &lt;/td&gt;&lt;td&gt;konversi meter         &lt;/td&gt;&lt;td&gt;penting untuk yield&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;allowance_qc  &lt;/td&gt;&lt;td&gt;0.03            &lt;/td&gt;&lt;td&gt;buffer reject          &lt;/td&gt;&lt;td&gt;3%–7% sesuai historis&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;tanggal_kirim &lt;/td&gt;&lt;td&gt;2026-02-25      &lt;/td&gt;&lt;td&gt;SLA                    &lt;/td&gt;&lt;td&gt;untuk evaluasi jadwal&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;data-konsumsi-kain-per-item&quot;&gt;Data Konsumsi Kain per Item&lt;/h4&gt;
&lt;p&gt;Buat tabel kecil lain (bisa spreadsheet) untuk “konsumsi standar” per kategori/ukuran.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;kategori      &lt;/th&gt;&lt;th&gt;ukuran &lt;/th&gt;&lt;th&gt;konsumsi_meter &lt;/th&gt;&lt;th&gt;catatan&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;seragam kerja &lt;/td&gt;&lt;td&gt;     M &lt;/td&gt;&lt;td&gt;           1.6 &lt;/td&gt;&lt;td&gt;termasuk kampuh&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;seragam kerja &lt;/td&gt;&lt;td&gt;     L &lt;/td&gt;&lt;td&gt;           1.7 &lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;jersey        &lt;/td&gt;&lt;td&gt;     M &lt;/td&gt;&lt;td&gt;           1.3 &lt;/td&gt;&lt;td&gt;tergantung pola&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;gamis         &lt;/td&gt;&lt;td&gt;     L &lt;/td&gt;&lt;td&gt;           2.6 &lt;/td&gt;&lt;td&gt;termasuk rok&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Di titik ini, Anda sudah siap menjalankan &lt;strong&gt;forecast kebutuhan bahan python&lt;/strong&gt; dengan cara yang realistis: mulai dari demand (qty) → convert ke meter kain → tambahkan buffer QC → hasilkan rencana pembelian.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-howto-scheme-dari-data-order-ke-rencana-beli-kain&quot;&gt;3. HowTo Scheme: Dari Data Order ke Rencana Beli Kain&lt;/h3&gt;
&lt;p&gt;Bab ini sengaja dibuat seperti &lt;em&gt;engineering playbook&lt;/em&gt;—biar bisa Anda copy ke repo internal.&lt;/p&gt;
&lt;h4 id=&quot;step-by-step-skema-praktis-&quot;&gt;Step-by-step (Skema Praktis)&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ambil data order&lt;/strong&gt; per hari/minggu dan segmentasi per kategori atau bahan.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Konversi qty → kebutuhan meter kain&lt;/strong&gt; (berdasarkan ukuran mix + konsumsi standar).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Buat time series&lt;/strong&gt; kebutuhan kain (meter) per bahan.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forecast 4–8 minggu ke depan&lt;/strong&gt; (pilih metode sederhana dulu).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hitung reorder point&lt;/strong&gt; = forecast demand selama lead time + safety stock.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generate purchase plan&lt;/strong&gt;: tanggal pesan, jumlah meter, prioritas supplier.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;template-python-ringkas-tapi-siap-jalan-&quot;&gt;Template Python (Ringkas Tapi Siap Jalan)&lt;/h4&gt;
&lt;blockquote&gt;&lt;p&gt;Fokus contoh ini: forecasting mingguan untuk satu bahan. Anda bisa loop untuk semua bahan.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pandas as pd
import numpy as np

# 1) Load data order yang sudah dikonversi ke kebutuhan meter per bahan
# kolom minimal: tanggal_order, bahan, kebutuhan_meter

df = pd.read_csv(&quot;order_kebutuhan_meter.csv&quot;)
df[&quot;tanggal_order&quot;] = pd.to_datetime(df[&quot;tanggal_order&quot;])

bahan_target = &quot;oxford 200gsm&quot;
ts = (
    df[df[&quot;bahan&quot;] == bahan_target]
    .set_index(&quot;tanggal_order&quot;)[&quot;kebutuhan_meter&quot;]
    .resample(&quot;W&quot;)
    .sum()
    .asfreq(&quot;W&quot;, fill_value=0)
)

# 2) Baseline forecast: moving average 4 minggu (simple &amp;amp; stable)
window = 4
forecast_horizon = 8

ma = ts.rolling(window=window, min_periods=1).mean()
last_ma = float(ma.iloc[-1])
forecast = pd.Series(
    [last_ma] * forecast_horizon,
    index=pd.date_range(ts.index[-1] + pd.Timedelta(weeks=1), periods=forecast_horizon, freq=&quot;W&quot;)
)

# 3) Reorder point (ROP)
lead_time_weeks = 2
service_factor = 1.65  # ~95% service level

hist = ts.tail(12)  # 3 bulan terakhir
sigma = float(hist.std(ddof=0))

expected_demand_lt = float(forecast.head(lead_time_weeks).sum())
safety_stock = service_factor * sigma * np.sqrt(lead_time_weeks)
rop = expected_demand_lt + safety_stock

print(&quot;Bahan:&quot;, bahan_target)
print(&quot;Forecast 8 minggu (meter/minggu):&quot;)
print(forecast.round(2))
print(&quot;\nROP (meter):&quot;, round(rop, 2))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Kenapa baseline ini cukup masuk akal?&lt;/strong&gt; Karena di operasional, &lt;em&gt;forecast&lt;/em&gt; yang “sedikit salah tapi stabil” sering lebih berguna daripada model kompleks yang susah dipelihara. Setelah baseline, baru naik level: &lt;em&gt;feature engineering&lt;/em&gt; (musiman, event), atau model time series lain.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Tip: Di CoderLegion ada contoh yang relevan untuk memahami pola forecasting dari data historis—lihat &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/10251/weather-service-project-part-3-predicting-the-future-with-ai-and-openweathermap&quot;&gt;Weather Service Project (Part 3): Predicting the Future with AI and OpenWeatherMap&lt;/a&gt; untuk gambaran alur “data → model → keputusan”.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Dalam praktik, Anda bisa menyebut pipeline ini sebagai &lt;strong&gt;forecast kebutuhan bahan python&lt;/strong&gt; versi MVP.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-metrik-yang-harus-anda-pantau-biar-forecast-tidak-jadi-pajangan-&quot;&gt;4. Metrik yang Harus Anda Pantau (Biar Forecast Tidak Jadi Pajangan)&lt;/h3&gt;
&lt;p&gt;Forecast yang bagus itu bukan yang terlihat keren di notebook, tapi yang memperbaiki keputusan pembelian dan produksi.&lt;/p&gt;
&lt;h4 id=&quot;kpi-operasional-yang-nyambung&quot;&gt;KPI Operasional yang Nyambung&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fill rate&lt;/strong&gt;: % order yang dipenuhi tanpa stockout.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lead time aktual&lt;/strong&gt;: dari PO ke bahan datang (per supplier).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Waste rate&lt;/strong&gt;: sisa kain/lembar cutting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;On-time delivery&lt;/strong&gt;: ketepatan kirim.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;error-metric-untuk-model-&quot;&gt;Error Metric (Untuk Model)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MAE&lt;/strong&gt; (Mean Absolute Error): mudah dipahami tim ops.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MAPE&lt;/strong&gt;: hati-hati kalau demand sering nol.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bias&lt;/strong&gt;: apakah forecast cenderung over/under.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kalau Anda baru mulai, cukup pantau: MAE + bias. Jalankan &lt;strong&gt;forecast kebutuhan bahan python&lt;/strong&gt; mingguan, lalu review tiap akhir minggu.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-studi-kasus-mini-seragam-industri-dengan-size-mix&quot;&gt;5. Studi Kasus Mini: Seragam Industri dengan Size Mix&lt;/h3&gt;
&lt;p&gt;Misal ada 3 klien industri minggu ini, total 420 pcs. Komposisi ukuran dan konsumsi standar memengaruhi kebutuhan kain.&lt;/p&gt;
&lt;h4 id=&quot;contoh-perhitungan-cepat&quot;&gt;Contoh Perhitungan Cepat&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Ukuran mix: M 50%, L 35%, XL 15%&lt;/li&gt;
&lt;li&gt;Konsumsi (meter): M 1.6, L 1.7, XL 1.85&lt;/li&gt;
&lt;li&gt;Allowance QC: 4%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kebutuhan meter (tanpa QC):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;M: 210 × 1.6 = 336&lt;/li&gt;
&lt;li&gt;L: 147 × 1.7 = 249.9&lt;/li&gt;
&lt;li&gt;XL: 63 × 1.85 = 116.55&lt;/li&gt;
&lt;li&gt;Total = 702.45 m&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dengan QC 4%: 702.45 × 1.04 = &lt;strong&gt;730.55 m&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Kalau Anda merekam perhitungan ini per minggu, Anda akan punya time series yang siap untuk &lt;strong&gt;forecast kebutuhan bahan python&lt;/strong&gt; tanpa ribet.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-faq-yang-sering-ditanya-developer-amp-owner-konveksi-&quot;&gt;6. FAQ (Yang Sering Ditanya Developer &amp;amp; Owner Konveksi)&lt;/h3&gt;
&lt;h4 id=&quot;apakah-harus-pakai-model-machine-learning-&quot;&gt;Apakah harus pakai model machine learning?&lt;/h4&gt;
&lt;p&gt;Tidak. Mulai dari baseline (moving average / exponential smoothing) dulu. ML masuk kalau Anda sudah punya data stabil dan ingin memanfaatkan fitur kontekstual.&lt;/p&gt;
&lt;h4 id=&quot;data-saya-bolong-bolong-bisa-&quot;&gt;Data saya bolong-bolong, bisa?&lt;/h4&gt;
&lt;p&gt;Bisa, asal Anda konsisten “mengisi nol” untuk periode tanpa order. Time series butuh kontinuitas.&lt;/p&gt;
&lt;h4 id=&quot;berapa-horizon-forecast-yang-realistis-&quot;&gt;Berapa horizon forecast yang realistis?&lt;/h4&gt;
&lt;p&gt;Untuk konveksi: 4–8 minggu biasanya paling actionable. Di atas itu, ketidakpastian naik (event/seasonality/negosiasi klien).&lt;/p&gt;
&lt;h4 id=&quot;bagaimana-kalau-ada-order-mendadak-besar-&quot;&gt;Bagaimana kalau ada order mendadak besar?&lt;/h4&gt;
&lt;p&gt;Tambahkan mekanisme &lt;em&gt;override&lt;/em&gt;: forecast baseline + “known future orders”. Ini lebih jujur daripada memaksa model mengerti kejutan.&lt;/p&gt;
&lt;h4 id=&quot;apa-kaitannya-dengan-produksi-bukan-cuma-beli-kain-&quot;&gt;Apa kaitannya dengan produksi (bukan cuma beli kain)?&lt;/h4&gt;
&lt;p&gt;Forecast bahan bisa diturunkan ke &lt;em&gt;capacity planning&lt;/em&gt;: estimasi jam jahit, kebutuhan operator, urutan produksi per deadline.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-closing-yang-lebih-engineering-dari-sekadar-operasional&quot;&gt;7. Closing yang Lebih “Engineering” dari Sekadar Operasional&lt;/h3&gt;
&lt;p&gt;Sebagai penutup, menarik mengingat analogi dari &lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Andrew_Ng&quot;&gt;Andrew Ng&lt;/a&gt;: &lt;strong&gt;“AI is the new electricity.”&lt;/strong&gt; Artinya: AI (dan praktik data-driven) akan menjadi &lt;em&gt;utility&lt;/em&gt; yang meresap ke banyak industri—bukan cuma tech company. Dalam konteks konveksi, “listrik” itu bisa berupa pipeline sederhana: data order → metrik → keputusan pembelian → jadwal produksi. Anda tidak harus mulai dari model canggih; yang penting adalah kebiasaan membuat keputusan berdasarkan data, lalu mengiterasinya.&lt;/p&gt;
&lt;p&gt;Kalau Anda ingin melihat implementasi nyata di lapangan (seragam kerja, jersey, pakaian muslim, hingga merchandise) dan bagaimana kebutuhan produksi diterjemahkan jadi eksekusi yang rapi, Anda bisa cek profil layanan kami di akhir tulisan ini—dan jadikan artikel ini sebagai fondasi &lt;em&gt;forecast kebutuhan bahan python&lt;/em&gt; yang dapat Anda adaptasi ke proyek internal.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Backlink (narasi yang wajar):&lt;/strong&gt; Jika Anda sedang mencari mitra produksi seragam/konveksi yang siap diajak kolaborasi berbasis data—mulai dari estimasi kebutuhan bahan sampai penjadwalan produksi—kunjungi &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.konveksikarawang.co.id/&quot;&gt;Konveksi Karawang — CV Mitra Mandiri Design&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;@context&quot;: &quot;https://schema.org&quot;,
  &quot;@type&quot;: &quot;Article&quot;,
  &quot;headline&quot;: &quot;Forecast Kebutuhan Bahan &amp;amp; Produksi Konveksi dengan Python (Praktis + Template)&quot;,
  &quot;description&quot;: &quot;Panduan praktis forecasting kebutuhan bahan dan perencanaan produksi konveksi dengan Python: dari data order, konversi konsumsi kain, baseline forecast, hingga reorder point.&quot;,
  &quot;inLanguage&quot;: &quot;id-ID&quot;,
  &quot;mainEntityOfPage&quot;: {
    &quot;@type&quot;: &quot;WebPage&quot;,
    &quot;@id&quot;: &quot;https://coderlegion.com/&quot;
  },
  &quot;author&quot;: {
    &quot;@type&quot;: &quot;Organization&quot;,
    &quot;name&quot;: &quot;CV Mitra Mandiri Design&quot;,
    &quot;url&quot;: &quot;https://www.konveksikarawang.co.id/&quot;
  },
  &quot;publisher&quot;: {
    &quot;@type&quot;: &quot;Organization&quot;,
    &quot;name&quot;: &quot;CV Mitra Mandiri Design&quot;,
    &quot;url&quot;: &quot;https://www.konveksikarawang.co.id/&quot;,
    &quot;logo&quot;: {
      &quot;@type&quot;: &quot;ImageObject&quot;,
      &quot;url&quot;: &quot;https://www.konveksikarawang.co.id/wp-content/uploads/&quot;
    }
  },
  &quot;about&quot;: [
    {&quot;@type&quot;: &quot;Thing&quot;, &quot;name&quot;: &quot;forecast kebutuhan bahan python&quot;},
    {&quot;@type&quot;: &quot;Thing&quot;, &quot;name&quot;: &quot;demand forecasting&quot;},
    {&quot;@type&quot;: &quot;Thing&quot;, &quot;name&quot;: &quot;production planning&quot;},
    {&quot;@type&quot;: &quot;Thing&quot;, &quot;name&quot;: &quot;python&quot;},
    {&quot;@type&quot;: &quot;Thing&quot;, &quot;name&quot;: &quot;konveksi&quot;}
  ],
  &quot;isAccessibleForFree&quot;: true
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12135/forecast-kebutuhan-bahan-produksi-konveksi-dengan-python-praktis-template</guid>
<pubDate>Sun, 08 Mar 2026 17:00:00 +0000</pubDate>
</item>
<item>
<title>Uncertainty &amp; Randomness — Why Data Is Never Perfec</title>
<link>https://coderlegion.com/12447/uncertainty-randomness-why-data-is-never-perfec</link>
<description>&lt;h3 id=&quot;uncertainty-amp-randomness-why-data-is-never-perfect&quot;&gt;&lt;strong&gt;Uncertainty &amp;amp; Randomness — Why Data Is Never Perfect&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;By &lt;strong&gt;Vyoma Youth Society&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;If we collect data carefully, why are we still uncertain?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the first lecture, we learned that data is not reality — it is a representation of reality.&lt;br&gt;
Today, we take one step further and ask:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Why can’t data ever be perfectly certain?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Why do two measurements of the same thing give different results?&lt;br&gt;
Why can’t we predict the future exactly?&lt;br&gt;
Why does error always exist?&lt;/p&gt;
&lt;p&gt;This brings us to two ideas:&lt;br&gt;
&lt;strong&gt;uncertainty&lt;/strong&gt; and &lt;strong&gt;randomness&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-every-measurement-has-error&quot;&gt;1️⃣ Every Measurement Has Error&lt;/h3&gt;
&lt;p&gt;No measurement is perfect.&lt;/p&gt;
&lt;p&gt;A thermometer does not show the exact temperature of the room.&lt;br&gt;
A survey does not show the exact opinion of society.&lt;br&gt;
A step counter does not count every movement of your body.&lt;/p&gt;
&lt;p&gt;Between reality and numbers, something is always lost.&lt;/p&gt;
&lt;p&gt;So uncertainty is not a mistake.&lt;br&gt;
It is unavoidable.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Uncertainty is part of measurement itself.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-randomness-vs-determinism&quot;&gt;2️⃣ Randomness vs Determinism&lt;/h3&gt;
&lt;p&gt;Some systems are predictable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a calculator&lt;/li&gt;
&lt;li&gt;a clock&lt;/li&gt;
&lt;li&gt;simple machines&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are called &lt;strong&gt;deterministic systems&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Some systems are unpredictable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;coin toss&lt;/li&gt;
&lt;li&gt;weather&lt;/li&gt;
&lt;li&gt;stock market&lt;/li&gt;
&lt;li&gt;human behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are called &lt;strong&gt;random systems&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;But here is a deep question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Is randomness part of nature, or part of our ignorance?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sometimes things look random because we do not know all the causes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-random-variable-turning-randomness-into-numbers&quot;&gt;3️⃣ Random Variable: Turning Randomness into Numbers&lt;/h3&gt;
&lt;p&gt;In data science, we turn uncertain events into numbers.&lt;br&gt;
These are called &lt;strong&gt;random variables&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Coin toss → 0 or 1&lt;/li&gt;
&lt;li&gt;Dice roll → 1 to 6&lt;/li&gt;
&lt;li&gt;Daily temperature → a number&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We cannot predict the exact value.&lt;br&gt;
But we can study how often values appear.&lt;/p&gt;
&lt;p&gt;So instead of predicting &lt;strong&gt;one number&lt;/strong&gt;,&lt;br&gt;
we study a &lt;strong&gt;distribution of numbers&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-order-from-chaos-the-average&quot;&gt;4️⃣ Order from Chaos: The Average&lt;/h3&gt;
&lt;p&gt;If we toss a coin once, the result is unpredictable.&lt;br&gt;
If we toss it many times, something interesting happens.&lt;/p&gt;
&lt;p&gt;The average slowly becomes stable near 0.5.&lt;/p&gt;
&lt;p&gt;This idea is called the &lt;strong&gt;law of large numbers&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It teaches us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;individual events are uncertain&lt;/li&gt;
&lt;li&gt;large patterns are stable&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;Chaos at small scale, order at large scale.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-variance-measuring-uncertainty&quot;&gt;5️⃣ Variance: Measuring Uncertainty&lt;/h3&gt;
&lt;p&gt;Two datasets can have the same average but different uncertainty.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dataset A: values are close together&lt;/li&gt;
&lt;li&gt;Dataset B: values are spread out&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both can have the same mean, but:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one is reliable&lt;/li&gt;
&lt;li&gt;one is risky&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Variance measures how spread out data is.&lt;/p&gt;
&lt;p&gt;Mean tells us the center.&lt;br&gt;
Variance tells us the uncertainty.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-why-this-matters&quot;&gt;6️⃣ Why This Matters&lt;/h3&gt;
&lt;p&gt;Uncertainty affects decisions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;weather prediction&lt;/li&gt;
&lt;li&gt;medical diagnosis&lt;/li&gt;
&lt;li&gt;economic policy&lt;/li&gt;
&lt;li&gt;social planning&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A prediction without uncertainty is dangerous.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Good data science does not remove uncertainty.&lt;br&gt;
It understands and measures it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;reflection-challenges&quot;&gt;Reflection Challenges&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Challenge 1:&lt;/strong&gt;&lt;br&gt;
Observe one uncertain thing in your daily life (weather, mood, traffic, steps).&lt;br&gt;
Write:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what changes&lt;/li&gt;
&lt;li&gt;what seems stable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Challenge 2:&lt;/strong&gt;&lt;br&gt;
Answer in one paragraph:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Is randomness in the world, or in our knowledge?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;connection-to-previous-lecture&quot;&gt;Connection to Previous Lecture&lt;/h3&gt;
&lt;p&gt;Lecture 1:&lt;br&gt;
Data is a representation of reality.&lt;/p&gt;
&lt;p&gt;Lecture 2:&lt;br&gt;
That representation is uncertain.&lt;/p&gt;
&lt;p&gt;Next Lecture will ask:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;What is a model, and why do we need it?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Vyoma Data Science Initiative&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Awareness • Mathematics • Open Knowledge&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/12447/uncertainty-randomness-why-data-is-never-perfec</guid>
<pubDate>Sat, 07 Mar 2026 07:46:37 +0000</pubDate>
</item>
<item>
<title>What Is Data, Really? - Module 1 vyoma data science initiative</title>
<link>https://coderlegion.com/12356/what-is-data-really-module-1-vyoma-data-science-initiative</link>
<description>&lt;h3 id=&quot;what-is-data-really-&quot;&gt;&lt;strong&gt;What Is Data, Really?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;By &lt;strong&gt;Vyoma Youth Society&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;“Can numbers represent truth?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Before moving forward, let us pause and ask:&lt;br&gt;
&lt;strong&gt;What is data, really?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And more importantly,&lt;br&gt;
Why has the world started saying that &lt;em&gt;data is the new power&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;Before reading further, try this simple exercise:&lt;br&gt;
Write down on a piece of paper what &lt;em&gt;you&lt;/em&gt; understand by the word &lt;strong&gt;data&lt;/strong&gt;.&lt;br&gt;
At the end of this post, look again at what you wrote and compare it with what you understand now.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;data-as-representation&quot;&gt;&lt;strong&gt;Data as Representation&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Data is not reality.&lt;br&gt;
It can never be reality.&lt;/p&gt;
&lt;p&gt;We cannot directly compare data with reality, because data is only a &lt;strong&gt;measurement of reality&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This may sound confusing at first, so let us observe something simple around us.&lt;/p&gt;
&lt;p&gt;Consider your phone charging.&lt;/p&gt;
&lt;p&gt;Your phone is part of reality.&lt;br&gt;
When you look at your phone and note its battery percentage — 20%, 40%, 75% — you are creating &lt;strong&gt;data&lt;/strong&gt; about your phone.&lt;/p&gt;
&lt;p&gt;Notice something important:&lt;br&gt;
You did not capture the phone itself.&lt;br&gt;
You captured a &lt;strong&gt;numerical description&lt;/strong&gt; of one aspect of the phone.&lt;/p&gt;
&lt;p&gt;This shows us that:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Data is reality expressed in numerical form.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In other words,&lt;br&gt;
&lt;strong&gt;data is a representation of reality, not reality itself.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-process-of-collecting-data&quot;&gt;&lt;strong&gt;The Process of Collecting Data&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Let us return to the phone charging example and break it into steps.&lt;/p&gt;
&lt;p&gt;What was the first thing we did?&lt;/p&gt;
&lt;p&gt;We &lt;strong&gt;observed&lt;/strong&gt; the phone.&lt;/p&gt;
&lt;p&gt;So the first step of collecting data is:&lt;/p&gt;
&lt;h4 id=&quot;1-observation&quot;&gt;1. Observation&lt;/h4&gt;
&lt;p&gt;Next, we converted that observation into a number (battery percentage).&lt;/p&gt;
&lt;h4 id=&quot;2-measurement&quot;&gt;2. Measurement&lt;/h4&gt;
&lt;p&gt;Finally, we wrote it down.&lt;/p&gt;
&lt;h4 id=&quot;3-recording&quot;&gt;3. Recording&lt;/h4&gt;
&lt;p&gt;So the process of collecting data looks like this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Observation → Measurement → Writing down&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is not just a practical process.&lt;br&gt;
It also works like a &lt;strong&gt;mathematical function&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;data-as-a-mathematical-mapping&quot;&gt;&lt;strong&gt;Data as a Mathematical Mapping&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;We can describe this process mathematically as:&lt;/p&gt;
&lt;p&gt;[
f: Reality \rightarrow Numbers
]&lt;/p&gt;
&lt;p&gt;This means:&lt;br&gt;
A function takes something from reality and converts it into a number.&lt;/p&gt;
&lt;p&gt;In simple symbolic form:&lt;/p&gt;
&lt;p&gt;[
\lambda(r) \rightarrow n
]&lt;/p&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;r = reality&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;n = number&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So whenever we collect data, we are applying a function that transforms reality into numbers.&lt;/p&gt;
&lt;p&gt;We will study this idea more deeply later when we discuss &lt;strong&gt;lambda calculus and mathematical modeling&lt;/strong&gt;.&lt;br&gt;
For now, just observe this transformation carefully.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;reflection-exercise&quot;&gt;&lt;strong&gt;Reflection Exercise&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Before the next session, do this exercise:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Choose one thing from your daily life (for example: sleep hours, steps walked, water intake, or phone usage).&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What exactly you are observing&lt;/li&gt;
&lt;li&gt;How you are measuring it&lt;/li&gt;
&lt;li&gt;What number you are recording&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then think:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What part of reality did you ignore?&lt;/li&gt;
&lt;li&gt;What assumptions did you make?&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;We will continue this discussion in the next class.&lt;/p&gt;
&lt;p&gt;For now, your task is not to calculate, but to &lt;strong&gt;think&lt;/strong&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Is data closer to truth, or only a structured description of truth?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;vyoma data science iniitiative by vyoma youth society&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/12356/what-is-data-really-module-1-vyoma-data-science-initiative</guid>
<pubDate>Wed, 04 Mar 2026 12:40:25 +0000</pubDate>
</item>
<item>
<title> Launching the Vyoma Data Science Initiative</title>
<link>https://coderlegion.com/12303/launching-the-vyoma-data-science-initiative</link>
<description>&lt;h2 id=&quot;launching-the-vyoma-data-science-initiative&quot;&gt;Launching the Vyoma Data Science Initiative&lt;/h2&gt;
&lt;h4 id=&quot;a-12-month-public-journey-into-mathematical-data-science&quot;&gt;A 12-Month Public Journey into Mathematical Data Science&lt;/h4&gt;
&lt;p&gt;Today, I’m starting something long-term.&lt;/p&gt;
&lt;p&gt;Not a tutorial series.&lt;br&gt;
Not a “learn pandas in 10 minutes” guide.&lt;br&gt;
Not another machine learning crash course.&lt;/p&gt;
&lt;p&gt;This is a structured, 12-month public journey into &lt;strong&gt;mathematical data science&lt;/strong&gt;, built under the vision of &lt;strong&gt;Vyoma Youth Society&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-initiative-exists&quot;&gt;Why This Initiative Exists&lt;/h3&gt;
&lt;p&gt;We live in a world where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data influences policy.&lt;/li&gt;
&lt;li&gt;Algorithms influence perception.&lt;/li&gt;
&lt;li&gt;Statistics influence belief.&lt;/li&gt;
&lt;li&gt;Machine learning influences markets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But very few people deeply understand how these systems actually work.&lt;/p&gt;
&lt;p&gt;Most learn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Libraries without theory.&lt;/li&gt;
&lt;li&gt;Models without mathematics.&lt;/li&gt;
&lt;li&gt;Predictions without philosophy.&lt;/li&gt;
&lt;li&gt;Tools without ethics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That gap is dangerous.&lt;/p&gt;
&lt;p&gt;So this initiative is built on a simple idea:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Data science is not just coding.&lt;br&gt;
It is a way of thinking about uncertainty, structure, and truth.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-this-series-will-be&quot;&gt;What This Series Will Be&lt;/h3&gt;
&lt;p&gt;Over the next 12 months, I will publish:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 structured module per month&lt;/li&gt;
&lt;li&gt;Mathematical derivations&lt;/li&gt;
&lt;li&gt;Python implementations from scratch&lt;/li&gt;
&lt;li&gt;Research-style mini projects&lt;/li&gt;
&lt;li&gt;Ethical discussions around data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This will be a progressive roadmap from:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Foundations of data thinking&lt;/li&gt;
&lt;li&gt;Probability and statistics&lt;/li&gt;
&lt;li&gt;Linear algebra for ML&lt;/li&gt;
&lt;li&gt;Optimization theory&lt;/li&gt;
&lt;li&gt;Machine learning from scratch&lt;/li&gt;
&lt;li&gt;Economic market simulations&lt;/li&gt;
&lt;li&gt;Data science for awareness and social impact&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Every module will contain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Theory&lt;/li&gt;
&lt;li&gt;Mathematical explanation&lt;/li&gt;
&lt;li&gt;Clean Python implementations&lt;/li&gt;
&lt;li&gt;A practical project&lt;/li&gt;
&lt;li&gt;A research challenge&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All code will be open-source in a public repository.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-core-philosophy&quot;&gt;The Core Philosophy&lt;/h3&gt;
&lt;p&gt;This initiative is built around three principles:&lt;/p&gt;
&lt;h4 id=&quot;1-first-principles-thinking&quot;&gt;1️⃣ First Principles Thinking&lt;/h4&gt;
&lt;p&gt;We won’t just “use” algorithms.&lt;br&gt;
We will derive them.&lt;/p&gt;
&lt;h4 id=&quot;2-mathematical-clarity&quot;&gt;2️⃣ Mathematical Clarity&lt;/h4&gt;
&lt;p&gt;Understanding why something works matters more than memorizing how to use it.&lt;/p&gt;
&lt;h4 id=&quot;3-ethical-awareness&quot;&gt;3️⃣ Ethical Awareness&lt;/h4&gt;
&lt;p&gt;Data science is powerful.&lt;br&gt;
Power requires responsibility.&lt;/p&gt;
&lt;p&gt;Under Vyoma Youth Society, this connects to a broader idea:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Awareness before influence.&lt;br&gt;
Understanding before optimization.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-makes-this-different-&quot;&gt;What Makes This Different?&lt;/h3&gt;
&lt;p&gt;This is not just a machine learning path.&lt;/p&gt;
&lt;p&gt;Later modules will include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simulating market behavior using demand–supply functions&lt;/li&gt;
&lt;li&gt;Modeling policy shocks&lt;/li&gt;
&lt;li&gt;Predicting equilibrium using ML&lt;/li&gt;
&lt;li&gt;Studying bias and misinformation patterns&lt;/li&gt;
&lt;li&gt;Building awareness-oriented analytical tools&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Data science will be treated as a discipline — not just a career skill.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;open-repository-structure&quot;&gt;Open Repository Structure&lt;/h3&gt;
&lt;p&gt;The full project will be organized month by month:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vyoma-data-science/
│
├── month01_foundations/
├── month02_python_math/
├── month03_probability/
...
├── month12_capstone/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each month = one conceptual layer.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;who-this-is-for&quot;&gt;Who This Is For&lt;/h3&gt;
&lt;p&gt;This series is for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Students who want depth, not shortcuts&lt;/li&gt;
&lt;li&gt;Developers who want mathematical clarity&lt;/li&gt;
&lt;li&gt;Thinkers who care about ethics&lt;/li&gt;
&lt;li&gt;Anyone curious about uncertainty and structure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You don’t need to know everything already.&lt;/p&gt;
&lt;p&gt;You just need curiosity and discipline.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;publishing-schedule&quot;&gt;Publishing Schedule&lt;/h3&gt;
&lt;p&gt;One module per month.&lt;br&gt;
Four structured posts per module.&lt;/p&gt;
&lt;p&gt;This is a long-term commitment.&lt;/p&gt;
&lt;p&gt;Not fast content.&lt;br&gt;
Structured growth.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-personal-note&quot;&gt;A Personal Note&lt;/h3&gt;
&lt;p&gt;I am not positioning myself as an “expert”.&lt;/p&gt;
&lt;p&gt;This is a documented journey into deeper mathematical data science.&lt;br&gt;
Everything will be transparent — mistakes included.&lt;/p&gt;
&lt;p&gt;The goal is growth, clarity, and contribution.&lt;/p&gt;
&lt;p&gt;If you want to walk this path — follow the series.&lt;/p&gt;
&lt;p&gt;Month 1 begins soon:&lt;br&gt;
&lt;strong&gt;Foundations of Data Thinking.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s build this properly.&lt;/p&gt;
&lt;p&gt;—&lt;br&gt;
&lt;em&gt;Vyoma Youth Society&lt;/em&gt;&lt;br&gt;
Awareness • Mathematics • Open Knowledge&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12303/launching-the-vyoma-data-science-initiative</guid>
<pubDate>Mon, 02 Mar 2026 06:03:29 +0000</pubDate>
</item>
<item>
<title>AWS Sagemaker Notebook Jobs for Accelerating Data Science Experimentation Workflows</title>
<link>https://coderlegion.com/12057/aws-sagemaker-notebook-jobs-for-accelerating-data-science-experimentation-workflows</link>
<description>&lt;p&gt;This post walks through a solution that combines Amazon SageMaker, MLflow, and Optuna to create an automated, scalable hyperparameter optimization pipeline.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12057/aws-sagemaker-notebook-jobs-for-accelerating-data-science-experimentation-workflows</guid>
<pubDate>Mon, 23 Feb 2026 06:15:54 +0000</pubDate>
</item>
<item>
<title>MS Excel for Data Analytics: A Beginner-Friendly Introduction</title>
<link>https://coderlegion.com/10370/ms-excel-for-data-analytics-a-beginner-friendly-introduction</link>
<description>&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z9msqc24iop3p2h1hfrv.png&quot; alt=&quot;MS Excel&quot;&gt;&lt;/p&gt;
&lt;p&gt;Howdy! Ever heard of &lt;strong&gt;Microsoft Excel&lt;/strong&gt;? Ever wondered what its use is? Worry not, let me simplify it for you. Microsoft Excel is one of the most poowerful and beginner-friendly tools for data analytics. Before people move to advanced tools like &lt;strong&gt;Python&lt;/strong&gt; or &lt;strong&gt;SQL&lt;/strong&gt;, they often start with Excel, and for good reason. It helps you &lt;strong&gt;organize data&lt;/strong&gt;, &lt;strong&gt;clean it&lt;/strong&gt;, &lt;strong&gt;analyze it&lt;/strong&gt;, and &lt;strong&gt;visualize it&lt;/strong&gt; all in one place. How cool is that?&lt;/p&gt;
&lt;p&gt;Whether you are tracking sales, survey responses, expenses, or social media performance, Excel can turn your &lt;strong&gt;raw numbers (data)&lt;/strong&gt; into meaningful insights.&lt;/p&gt;
&lt;h3 id=&quot;what-you-will-learn-from-this-article&quot;&gt;What You Will Learn from This Article&lt;/h3&gt;
&lt;p&gt;By the end of this article, you will be able to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understand what Microsoft Excel is and how it is used in data analytics&lt;/li&gt;
&lt;li&gt;Identify rows, columns, and cells in a worksheet&lt;/li&gt;
&lt;li&gt;Enter and organize data in table format&lt;/li&gt;
&lt;li&gt;Use basic Excel functions like SUM, AVERAGE, COUNT, and IF&lt;/li&gt;
&lt;li&gt;Clean data by sorting, filtering, and removing duplicates&lt;/li&gt;
&lt;li&gt;Create Pivot Tables to summarize large amounts of data&lt;/li&gt;
&lt;li&gt;Build simple charts to visualize trends and comparisons&lt;/li&gt;
&lt;li&gt;Understand how Excel can be used to create basic dashboards for reporting&lt;/li&gt;
&lt;li&gt;Gain a strong foundation to start working with data confidently, even as a beginner.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;excel-terms-you-will-encounter-excel-amp-data-analytics-&quot;&gt;Excel Terms You Will Encounter (Excel &amp;amp; Data Analytics)&lt;/h3&gt;
&lt;p&gt;Here are simple explanations of key terms used in this article:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cell&lt;/strong&gt; - A single box in Excel where a row and column meet (example: A1).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Column&lt;/strong&gt; - A vertical group of cells labeled with letters (A, B, C…).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Row&lt;/strong&gt; - A horizontal group of cells labeled with numbers (1, 2, 3…).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Worksheet&lt;/strong&gt; - One page inside an Excel file where you enter and analyze data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workbook&lt;/strong&gt; - An Excel file that contains one or more worksheets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dataset&lt;/strong&gt; - A collection of related data arranged in rows and columns.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Formula&lt;/strong&gt; - An equation used in Excel to perform calculations (example: =A1+B1).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Function&lt;/strong&gt; - A built-in formula in Excel that performs a specific task (example: SUM, AVERAGE).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Range&lt;/strong&gt; - A group of selected cells (example: A1:A10).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filter&lt;/strong&gt; - A tool used to display only the data that meets certain conditions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sort&lt;/strong&gt; - Arranging data in a specific order (A–Z, smallest to largest, etc.).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pivot Table&lt;/strong&gt; - A tool that summarizes and analyzes large datasets by grouping and calculating data automatically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chart&lt;/strong&gt; - A visual representation of data, such as a bar chart or line graph.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dashboard&lt;/strong&gt; - A single page that shows key data insights using charts, numbers, and summaries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Cleaning&lt;/strong&gt; - The process of fixing or organizing data so it is accurate and ready for analysis.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All right, let’s get into understanding MS Excel.&lt;/p&gt;
&lt;h3 id=&quot;1-introduction-to-excel&quot;&gt;1. Introduction to Excel&lt;/h3&gt;
&lt;h4 id=&quot;definition&quot;&gt;Definition&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Microsoft Excel&lt;/strong&gt; is a spreadsheet software developed by Microsoft that helps people &lt;strong&gt;store&lt;/strong&gt;, &lt;strong&gt;organize&lt;/strong&gt;, &lt;strong&gt;calculate&lt;/strong&gt;, and &lt;strong&gt;analyze&lt;/strong&gt; data using tables made of &lt;strong&gt;rows&lt;/strong&gt; and &lt;strong&gt;columns&lt;/strong&gt;.&lt;br&gt;
It allows users to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Perform calculations with formulas and functions&lt;/li&gt;
&lt;li&gt;Sort and filter information&lt;/li&gt;
&lt;li&gt;Create charts and graphs&lt;/li&gt;
&lt;li&gt;Summarize data using tools like Pivot Tables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Simply put, Excel turns raw numbers into useful information that helps with decision-making.&lt;/p&gt;
&lt;p&gt;Excel is a spreadsheet program made up of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rows – horizontal lines (1, 2, 3…)&lt;/li&gt;
&lt;li&gt;Columns – vertical lines (A, B, C…)&lt;/li&gt;
&lt;li&gt;Cells – where rows and columns meet (like A1, B2)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each cell can contain:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Text (Names, Categories)&lt;/li&gt;
&lt;li&gt;Numbers (Sales, Scores)&lt;/li&gt;
&lt;li&gt;Dates (1/1/2026, 02-11-2026)&lt;/li&gt;
&lt;li&gt;Formulas (calculations), among many others.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yaywd91016xaqavcn37u.png&quot; alt=&quot;Rows, Columns &amp;amp; Cells&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;example-dataset&quot;&gt;Example Dataset&lt;/h4&gt;
&lt;p&gt;Let’s say we have this simple sales dataset:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Date       &lt;/th&gt;&lt;th&gt;Product &lt;/th&gt;&lt;th&gt;Quantity &lt;/th&gt;&lt;th&gt;Price  &lt;/th&gt;&lt;th&gt;Total Sales&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1/1/2026 &lt;/td&gt;&lt;td&gt;Pen     &lt;/td&gt;&lt;td&gt;      10 &lt;/td&gt;&lt;td&gt;    15 &lt;/td&gt;&lt;td&gt;        150&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1/2/2026 &lt;/td&gt;&lt;td&gt;Book    &lt;/td&gt;&lt;td&gt;       5 &lt;/td&gt;&lt;td&gt;    70 &lt;/td&gt;&lt;td&gt;        350&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2/3/2026 &lt;/td&gt;&lt;td&gt;Pencil  &lt;/td&gt;&lt;td&gt;       7 &lt;/td&gt;&lt;td&gt;    25 &lt;/td&gt;&lt;td&gt;        175&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1/4/2026 &lt;/td&gt;&lt;td&gt;Rubber  &lt;/td&gt;&lt;td&gt;       3 &lt;/td&gt;&lt;td&gt;    10 &lt;/td&gt;&lt;td&gt;         30&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;*&lt;strong&gt;NOTE:&lt;/strong&gt; In this table, the Total Sales column is calculated by multiplying Quantity × Price.*&lt;/p&gt;
&lt;p&gt;This kind of table is the starting point for data analysis.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4mrucjfgcdw9yw5s8ob3.png&quot; alt=&quot;Example Dataset&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-excel-functions-basic-calculations-&quot;&gt;2. Excel Functions (Basic Calculations)&lt;/h3&gt;
&lt;p&gt;Functions are built-in formulas that help you perform calculations easily.&lt;/p&gt;
&lt;h4 id=&quot;1-sum-add-numbers&quot;&gt;1. SUM – Add Numbers&lt;/h4&gt;
&lt;p&gt;Adds a range of numbers.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;=SUM(C2:C5)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This &lt;strong&gt;adds&lt;/strong&gt; all values from cell &lt;strong&gt;C2&lt;/strong&gt; to &lt;strong&gt;C5&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;*&lt;strong&gt;Use case:&lt;/strong&gt; Total sales, total expenses, total quantity sold.*&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7zxzsrtxx0lg78yht99g.png&quot; alt=&quot;Sum Func&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;average-find-the-mean&quot;&gt;AVERAGE – Find the Mean&lt;/h4&gt;
&lt;p&gt;Calculates the average of numbers.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;=AVERAGE(D2:D5)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;*&lt;strong&gt;Use case:&lt;/strong&gt; Average score, average daily sales.*&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vg7qu9obc1t1kyg9x8kt.png&quot; alt=&quot;AVG Func&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;count-count-numbers&quot;&gt;COUNT – Count Numbers&lt;/h4&gt;
&lt;p&gt;Counts how many cells contain numbers.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;=COUNT(A2:A5)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;*&lt;strong&gt;Use case:&lt;/strong&gt; Number of transactions recorded.*&lt;/p&gt;
&lt;h4 id=&quot;counta-count-non-empty-cells&quot;&gt;COUNTA – Count Non-Empty Cells&lt;/h4&gt;
&lt;p&gt;Counts cells that are not empty (text or numbers).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;=COUNTA(B2:B5)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;*&lt;strong&gt;Use case:&lt;/strong&gt; Number of customers or products listed.*&lt;/p&gt;
&lt;h4 id=&quot;if-logical-function&quot;&gt;IF – Logical Function&lt;/h4&gt;
&lt;p&gt;Performs a test and returns different results depending on the outcome.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;=IF(C2&amp;gt;10, &quot;High&quot;, &quot;Low&quot;)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This checks if the value in C2 is greater than 10.&lt;/p&gt;
&lt;p&gt;*&lt;strong&gt; Use case&lt;/strong&gt; Categorizing sales as High/Low, Pass/Fail.*&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u3mkov054oxqi8c58pc5.png&quot; alt=&quot;If Func&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;3-data-cleaning-in-excel&quot;&gt;3. Data Cleaning in Excel&lt;/h3&gt;
&lt;p&gt;Before analysis, data often needs &lt;strong&gt;cleaning&lt;/strong&gt;.&lt;br&gt;
Common cleaning tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Removing duplicates&lt;br&gt;
&lt;strong&gt;Data -&amp;gt; Remove Duplicates&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Sorting data&lt;br&gt;
&lt;strong&gt;Data -&amp;gt; Sort A to Z&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Filtering data&lt;br&gt;
&lt;strong&gt;Data -&amp;gt; Filter&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o9tpl5upim8uz5bs0gcl.png&quot; alt=&quot;Dups, sort, filter&quot;&gt;&lt;/p&gt;
&lt;p&gt;Filtering lets you view only certain data, like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only sales for “Pen.”&lt;/li&gt;
&lt;li&gt;Only records from January&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-pivot-tables-powerful-data-summaries-&quot;&gt;4. Pivot Tables (Powerful Data Summaries)&lt;/h3&gt;
&lt;p&gt;Pivot Tables help you summarize large data quickly without complex formulas.&lt;/p&gt;
&lt;p&gt;You can answer questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Total sales per product&lt;/li&gt;
&lt;li&gt;Sales per month&lt;/li&gt;
&lt;li&gt;Quantity sold by category&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;steps-to-create-a-pivot-table-&quot;&gt;Steps to create a Pivot Table:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Select your &lt;strong&gt;data&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Insert -&amp;gt; PivotTable&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Choose where to place it&lt;/li&gt;
&lt;li&gt;Drag fields into Rows, Columns, and Values&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rows -&amp;gt; Product&lt;/li&gt;
&lt;li&gt;Values -&amp;gt; Sum of Total Sales&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Excel will automatically calculate total sales for each product.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m4ztwb1ubrwus3dpc5er.png&quot; alt=&quot;Pivot Table&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;5-charts-in-excel&quot;&gt;5. Charts in Excel&lt;/h3&gt;
&lt;p&gt;Charts help turn numbers into visuals that are easier to understand.&lt;/p&gt;
&lt;h4 id=&quot;common-chart-types-&quot;&gt;Common chart types:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Column Chart&lt;/strong&gt; – Compares categories&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Line Chart&lt;/strong&gt; – Shows trends over time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pie Chart&lt;/strong&gt; – Shows proportions&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;how-to-insert-a-chart-&quot;&gt;How to insert a chart:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Select your data&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Insert&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Choose a chart type&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;*&lt;strong&gt;Example:&lt;/strong&gt; Sales by product displayed as a column chart.*&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gtd20ds58i4mbf6glibx.png&quot; alt=&quot;Chart&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;6-dashboards-in-excel&quot;&gt;6. Dashboards in Excel&lt;/h3&gt;
&lt;p&gt;A &lt;strong&gt;dashboard&lt;/strong&gt; is a single page that shows key insights using charts, numbers, and summaries.&lt;/p&gt;
&lt;p&gt;A simple Excel dashboard might include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Total Sales (big number)&lt;/li&gt;
&lt;li&gt;Sales by Product (chart)&lt;/li&gt;
&lt;li&gt;Sales by Month (line chart)&lt;/li&gt;
&lt;li&gt;Top Product (text or card)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dashboards help decision-makers understand performance quickly.&lt;/p&gt;
&lt;h4 id=&quot;tools-used-in-dashboards-&quot;&gt;Tools used in dashboards:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Pivot Tables&lt;/li&gt;
&lt;li&gt;Charts&lt;/li&gt;
&lt;li&gt;Slicers (interactive filters)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g3o86fvt21ft8xe15zxt.png&quot; alt=&quot;Dashboard&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;conclusion-5&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Excel is a powerful starting tool for data analytics because it helps you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Organize data in tables&lt;/li&gt;
&lt;li&gt;Perform calculations using functions&lt;/li&gt;
&lt;li&gt;Clean and filter messy data&lt;/li&gt;
&lt;li&gt;Summarize information with Pivot Tables&lt;/li&gt;
&lt;li&gt;Visualize trends using charts&lt;/li&gt;
&lt;li&gt;Build dashboards for reporting&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mastering Excel gives you a strong foundation before moving into advanced analytics tools like Power BI, Python, or SQL.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;ALL THE BEST IN YOUR ANALYTICS JOURNEY!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/10370/ms-excel-for-data-analytics-a-beginner-friendly-introduction</guid>
<pubDate>Fri, 23 Jan 2026 20:51:23 +0000</pubDate>
</item>
</channel>
</rss>