← Back to recipes

Match volunteers to roles automatically

service-deliveryintermediateproven

The problem

You've got 50 volunteers with different skills, availability, and interests, and 20 roles to fill. Manually matching people to roles means scrolling through spreadsheets trying to remember who said they could do Wednesdays and who has graphic design skills. People end up in roles that don't suit them, or willing volunteers don't get asked because you forgot about them.

The solution

Build a simple matching algorithm that scores volunteers against role requirements. It considers: skills match, availability overlap, interest alignment, experience level, and location if relevant. You get a ranked list of best-fit volunteers for each role. Instead of guessing, you see 'Sarah is 85% match for event coordinator based on skills and availability'.

What you get

For each volunteer role, a ranked list of volunteers scored by how well they match. Shows why each person is a good fit: 'Maria: 90% match - has required skills (events, social media), available Tue/Wed, interested in youth work, lives locally'. Helps you make informed placement decisions and ensures you don't overlook suitable people.

Before you start

  • Volunteer data: skills, availability (days/times), interests, experience, location
  • Role requirements: required skills, time commitments, responsibilities
  • Data in spreadsheet format (CSV)
  • A Google account for Colab
  • Basic Python skills or willingness to adapt example code
  • IMPORTANT: Anonymise volunteer names before uploading to Google Colab - use IDs or pseudonyms. Check your organisation's privacy policy covers using volunteer PII for this purpose

When to use this

  • You manage more than 20-30 volunteers and struggle to track who fits where
  • You have multiple roles with different requirements
  • Volunteers complain about poor role fit or not being asked
  • You spend hours manually matching people to opportunities

When not to use this

  • You only have a handful of volunteers - manual matching is quicker
  • All your volunteers do the same thing so there's nothing to match
  • Role requirements are too subjective to quantify (needs 'good energy')
  • You don't have structured data on volunteer skills and availability

Steps

  1. 1

    Structure your volunteer data

    Create a spreadsheet with columns: volunteer name, skills (comma-separated list), availability (days of week), interests, experience level (beginner/intermediate/experienced), location. Be consistent with naming - 'event planning' not 'events' and 'event co-ordination'.

  2. 2

    Structure your role requirements

    Create another spreadsheet with: role name, required skills, preferred skills, time requirement (days/hours per week), availability needed, location, experience level needed. Use the same skill names as your volunteer data.

  3. 3

    Define your matching criteria

    Decide what matters most: exact skill match (must-have), nice-to-have skills (bonus points), availability overlap (essential), experience level match, location proximity. Assign weights: maybe skills are 40%, availability 30%, experience 20%, location 10%.

  4. 4

    Run the matching algorithm

    Use the example code to calculate match scores. The algorithm compares each volunteer against each role using weighted set-based matching: it calculates what proportion of required skills a volunteer has, checks availability overlap, and combines these with configurable weights.

  5. 5

    Review top matches

    For each role, look at the top 5 matches. Do they make sense? Is someone with 95% match actually a good fit, or did the algorithm miss something important? Check for potential bias: is the algorithm unfairly favouring certain locations or experience levels that might exclude specific demographics? Use the scores as guidance, not gospel. You still need human judgement.

  6. 6

    Refine the weights

    If matches feel wrong, adjust your weights. Maybe availability should matter more than skills for some roles. Or experience level is critical. Re-run with different weights until results feel sensible. This is as much art as science.

  7. 7

    Reach out to volunteers

    Contact your top matches for each role. Explain why you think they'd be a good fit. Even people who didn't know they had relevant skills will appreciate being noticed. The algorithm surfaces matches you might have overlooked.

  8. 8

    Update your data regularly(optional)

    As volunteers gain skills or change availability, update your spreadsheet. Re-run matching quarterly or when new roles come up. Keep your volunteer data current so matches stay relevant.

Example code

Match volunteers to roles by skills and availability

This calculates match scores between volunteers and roles. Adapt the weights and criteria to your organisation's needs.

import pandas as pd
import numpy as np

# Load your data
volunteers = pd.read_csv('volunteers.csv')
roles = pd.read_csv('roles.csv')

print(f"Loaded {len(volunteers)} volunteers and {len(roles)} roles")

# Example volunteer data structure:
# name, skills, availability, interests, experience, location
# "Sarah", "events, social media, youth work", "Tue,Wed,Thu", "youth work", "intermediate", "Manchester"

# Example role data structure:
# role_name, required_skills, preferred_skills, availability_needed, experience_needed, location
# "Event Coordinator", "events, communication", "social media", "Tue,Wed", "intermediate", "Manchester"

def calculate_skill_match(volunteer_skills, required_skills, preferred_skills):
    """Calculate how well volunteer skills match role requirements"""
    volunteer_skills_set = set([s.strip().lower() for s in volunteer_skills.split(',')])
    required_set = set([s.strip().lower() for s in required_skills.split(',')])
    preferred_set = set([s.strip().lower() for s in preferred_skills.split(',')]) if pd.notna(preferred_skills) else set()

    # Must have all required skills
    required_match = len(volunteer_skills_set & required_set) / len(required_set) if required_set else 0

    # Bonus for preferred skills
    preferred_match = len(volunteer_skills_set & preferred_set) / len(preferred_set) if preferred_set else 0

    # Weighted score: required skills are critical, preferred are bonus
    return (required_match * 0.8) + (preferred_match * 0.2)

def calculate_availability_match(volunteer_days, role_days):
    """Check if volunteer availability overlaps with role needs"""
    vol_days = set([d.strip() for d in volunteer_days.split(',')])
    role_days = set([d.strip() for d in role_days.split(',')])

    overlap = len(vol_days & role_days)
    needed = len(role_days)

    return overlap / needed if needed > 0 else 0

def calculate_experience_match(volunteer_exp, role_exp):
    """Check if experience level is appropriate"""
    exp_levels = ['beginner', 'intermediate', 'experienced']

    try:
        vol_level = exp_levels.index(volunteer_exp.strip().lower())
        role_level = exp_levels.index(role_exp.strip().lower())

        # Perfect match is 1.0, one level off is 0.5, two levels is 0
        diff = abs(vol_level - role_level)
        return max(0, 1 - (diff * 0.5))
    except (ValueError, AttributeError):
        return 0.5  # Default if experience not specified

def calculate_location_match(volunteer_loc, role_loc):
    """Simple location match (same city/area)"""
    if pd.isna(volunteer_loc) or pd.isna(role_loc):
        return 0.5  # Neutral if location not specified

    return 1.0 if volunteer_loc.strip().lower() == role_loc.strip().lower() else 0.3

# Match each volunteer to each role
matches = []

for _, role in roles.iterrows():
    print(f"\nMatching volunteers to: {role['role_name']}")

    role_matches = []

    for _, volunteer in volunteers.iterrows():
        # Calculate individual scores
        skill_score = calculate_skill_match(
            volunteer['skills'],
            role['required_skills'],
            role.get('preferred_skills', '')
        )

        availability_score = calculate_availability_match(
            volunteer['availability'],
            role['availability_needed']
        )

        experience_score = calculate_experience_match(
            volunteer['experience'],
            role['experience_needed']
        )

        location_score = calculate_location_match(
            volunteer.get('location'),
            role.get('location')
        )

        # Weighted overall match score
        # Adjust these weights based on what matters for your organisation
        overall_score = (
            skill_score * 0.40 +           # Skills most important
            availability_score * 0.30 +    # Then availability
            experience_score * 0.20 +      # Then experience level
            location_score * 0.10          # Location nice-to-have
        )

        role_matches.append({
            'role': role['role_name'],
            'volunteer': volunteer['name'],
            'overall_score': overall_score,
            'skill_match': skill_score,
            'availability_match': availability_score,
            'experience_match': experience_score,
            'location_match': location_score,
            'volunteer_skills': volunteer['skills'],
            'volunteer_availability': volunteer['availability']
        })

    # Sort by overall score
    role_matches = sorted(role_matches, key=lambda x: x['overall_score'], reverse=True)

    # Show top 5 matches for this role
    print(f"\nTop 5 matches for {role['role_name']}:")
    for i, match in enumerate(role_matches[:5], 1):
        print(f"{i}. {match['volunteer']}: {match['overall_score']:.0%} match")
        print(f"   Skills: {match['skill_match']:.0%} | Availability: {match['availability_match']:.0%} | Experience: {match['experience_match']:.0%}")
        print(f"   Has: {match['volunteer_skills']}")
        print(f"   Available: {match['volunteer_availability']}")

    matches.extend(role_matches)

# Export all matches
matches_df = pd.DataFrame(matches)
matches_df = matches_df.sort_values(['role', 'overall_score'], ascending=[True, False])
matches_df.to_csv('volunteer_role_matches.csv', index=False)

print("\n" + "="*60)
print("Matching complete! Saved to volunteer_role_matches.csv")
print("\nTips:")
print("- Adjust the weights (0.40, 0.30, 0.20, 0.10) based on what matters most")
print("- Check if top matches make intuitive sense")
print("- Use scores as guidance, not absolute decisions")
print("- Consider creating a 'shortlist' of top 3 for each role to contact")

Tools

Google Colabplatform · freemium
Visit →
pandaslibrary · free · open source
Visit →
scikit-learnlibrary · free · open source
Visit →

Resources

At a glance

Time to implement
days
Setup cost
free
Ongoing cost
free
Cost trend
stable
Organisation size
medium, large
Target audience
operations-manager, program-delivery

All tools are free. Initial setup takes a few hours. Once built, matching new volunteers takes seconds.