How can I group entries based on a string value? I have been able to successfully group entries via dateTime field. I have been trying to carry over the same type of logic only instead of using .date just key off of the field value.

{# Get all entries from the section. #}
{% set entries = craft.entries.section('mySection') %}

{# Loop through the entries and get the user account associated with each. #}
{% for profiles in entries %}
    {% set users = profiles.userAccount %}

    {% if users|length %}
        {% for year, user in users|group('myYearField') %}
            &lt;h4&gt;{{ year }}&lt;/h4&gt; {# 2016 #}
            &lt;ul class="list-unstyled"&gt;
                {% for user in users %}
                    &lt;li&gt;&lt;a href="{{ profiles.url }}"&gt;{{ user.name }}&lt;/a&gt;&lt;/li&gt;
                {% endfor %}
            &lt;/ul&gt;
        {% endfor %}
    {% endif %}
{% endfor %}


Currently what I am getting is:

2016
    User
2016
    User
...


What I am trying to get (I swear I had this working at one point) is this:

2016
    User
    User

2017
   User
   User


EDIT

It seems my trouble comes from not accessing the users field correctly.
Updated (but still incorrect) example:

{% set allProfiles = craft.entries.section('mySection') %}
{% if allProfiles|length %}
    {{ allProfiles|length }} {# 3 #}
    {% for profiles in allProfiles %}
        {% for profile in profiles.userAccount %}
            {% for year, user in profile|group('yearFieldHandle') %}
                &lt;h1&gt;{{ year }}&lt;/h1&gt;  {# No output #}
                &lt;h2&gt;{{ profile.firstName }}&lt;/h2&gt;    {# Bob #}
                &lt;h3&gt;{{ profile.myYear }}&lt;/h3&gt; {# 2016 #}
            {% endfor %}
        {% endfor %}
    {% endfor %}
{% endif %}
There’s a lot to unpack here. Before I get to the solution, I’ll step through each of your examples, line-by-line. Hopefully that will help explain the behavior you’re seeing.

First Example

{# Get all entries from the section. #}
{% set entries = craft.entries.section('mySection') %}


Your comment is correct – this will fetch all the entries from section mySection and assign them to a new variable called entries.

{# Loop through the entries and get the user account associated with each. #}
{% for profiles in entries %}


Start a loop on the entries variable. Within the loop, each profile entry will be represented by a profiles variable.

Note: it’s misleading to call your variable “profiles” rather than “profile”, since it will only represent a single profile entry.

    {% set users = profiles.userAccount %}


Fetch all of the user accounts related to the current profile (profiles) via the userAccount field, and assign them to a new variable called users.

    {% if users|length %}


Only do the following if there were any related users…

        {% for year, user in users|group('myYearField') %}


Turn the users (users) that are related to the current profile (profiles) into a multi-dimensional array, grouped by their myYearField field values. Then start a loop on that multi-dimensional array. Within the loop, each myYearField value will be represented by a year variable, and the array of grouped user accounts will be represented by a user variable.

Note: This is where things start really going wrong. You want to group all of your users by year – not just the ones that are related to the current profile. Also, your choice of “user” as a variable name here is misleading since it’s actually an array of grouped users; not a single user.

            &lt;h4&gt;{{ year }}&lt;/h4&gt; {# 2016 #}


Output the current grouping’s myYearField value, which was assigned to a year variable.

            &lt;ul class="list-unstyled"&gt;
                {% for user in users %}


Start a loop on the same users variable that you’re already in the middle of looping through. users here refers to the same users variable that was defined in the 3rd codeblock.

                    &lt;li&gt;&lt;a href="{{ profiles.url }}"&gt;{{ user.name }}&lt;/a&gt;&lt;/li&gt;


Output the profile entry’s (profiles) URL, and the current user’s (user) name.

                {% endfor %}


Close out the loop on the user accounts related to the current profile entry (users).

            &lt;/ul&gt;
        {% endfor %}


Close out the loop on the multi-dimensional array of year-user groupings based on the user accounts related to the current profile entry (users).

    {% endif %}


Close out the conditional checking if there any users related to the current profile entry.

{% endfor %}


Close out the loop on the profile entries (entries).



Second Example

{% set allProfiles = craft.entries.section('mySection') %}


Fetch all the entries from section mySection and assign them to a new variable called allProfiles.

{% if allProfiles|length %}


Only do the following if there were any profile entries…

    {{ allProfiles|length }} {# 3 #}


Output the total number of profile entries.

    {% for profiles in allProfiles %}


Start a loop on the allProfiles variable. Within the loop, each profile entry will be represented by a profiles variable.

Note: as with the last example, it’s misleading to call your variable “profiles” rather than “profile”, since it will only represent a single profile entry.

        {% for profile in profiles.userAccount %}


Fetch all of the user accounts related to the current profile (profiles) via the userAccount field. Start a loop on the user accounts. Within the loop, each user account will be represented by a profile variable.

Note: it’s misleading to call your variable “profile”, since it is actually the user account, not the profile entry.

            {% for year, user in profile|group('yearFieldHandle') %}


Turn the current user account (profile) into a multi-dimensional array, grouped by its properties’ yearFieldHandle sub-property values (which isn’t a thing). Then start a loop on that multi-dimensional array. Within the loop, each yearFieldHandle value will be represented by a year variable, and the array of grouped user account properties will be represented by a user variable.

Note: Since profile is a single element (the user account) and not an array of elements, nothing about this is likely doing what you expected it to.

                &lt;h1&gt;{{ year }}&lt;/h1&gt;  {# No output #}


Output the current grouping’s yearFieldHandle value, which was assigned to a year variable. (This is the equivalent of doing something like profile.id.yearFieldHandle. It’s expected that you wouldn’t get any output.)

                &lt;h2&gt;{{ profile.firstName }}&lt;/h2&gt;    {# Bob #}


Output the user account’s first name.

                &lt;h3&gt;{{ profile.myYear }}&lt;/h3&gt; {# 2016 #}


Output the user account’s myYear custom field value.

            {% endfor %}


Close out the loop on the multi-dimensional array of year-user account property groupings based on the current user account defined by the parent loop.

        {% endfor %}


Close out the loop on the user accounts related to the current profile entry.

    {% endfor %}


Close out the loop on the profile entries (allProfiles).

{% endif %}


Close out the conditional checking if there any profile entries.



Solution

To group all of your profiles by their related user accounts’ custom date fields, first let’s load the profiles, eager-loaded with their related user accounts:

{% set profiles = craft.entries({
    section: 'userProfiles',
    with: ['userAccount']
}) %}


Next let’s group the profiles by the myYear custom field on their related user accounts:

{% set profilesByYear = profiles|group('userAccount[0].myYear') %}


Now we can loop through the years, and their corresponding user profiles/accounts:

{% for year, yearProfiles in profilesByYear %}
    &lt;h1&gt;{{ year }}&lt;/h1&gt;

    {% for profile in yearProfiles %}
        {% set user = profile.userAccount[0] ?? null %}
        {% if user %}
            &lt;h2&gt;{{ user.firstName }}&lt;/h2&gt;
            &lt;h3&gt;{{ user.myYear }}&lt;/h3&gt;
        {% endif %}
    {% endfor %}
{% endfor %}