Вопрос: Насколько допустимо с точки зрения стилистики вызывать Exception в конструкции if/else?

Лучший ответ: Вопрос не очень понятен, особенно в части "обрабатывать" можно как через try/catch".

Если я правильно понял, то задача - выбросить кастомное исключение, и выбор между проверкой через if и поимкой встроенного TypeError с последующим перевыбросом исключения со своим текстом ошибки.

В этом случае первый вариант однозначно предпочтительнее.
При ловле исключения будет более сложная логика - его надо не только поймать, но и определить что это именно то, которое мы ждём. И если это какое-то другое, то перевыбросить без изменений. 
Кроме того, исключения в основном используются в исключительных, непредвиденных ситуациях. А здесь случай вполне предвиденный. 

Я бы только инвертировал условие, чтобы во-первых, сделать логику более стройной (проверили - вывалились), а во-вторых, чтобы избавиться от else и убрать лишний отступ. 
if not (isinstance(data_set, list) or isinstance(data_set, tuple)): 
    raise TypeError('argument must be list or tuple')
return sum(data_set) / len(data_set)

Но в целом я бы использовал другой подход.
Если мы хотим свой собственный текст ошибки, то это явно намекает на валидацию данных с последующим информированием пользователя об ошибке.
А чтобы сделать валидацию, исключения не нужны. Мы просто проверяем полученные данные и выдаём ошибку. 

То есть в реальном коде я бы убрал проверку из функции вообще, а проверял данные при их получении (и выводил ошибку). 
А на случай, если вдруг функция всё равно будет вызвана с неверным типом аргумента, есть системное исключение, 'type' object is not iterable

Худший ответ: Подход, предложенный @Ипатьев, в принципе неплох.
Но я считаю, стоит помнить, что питон - язык с динамической типизацией. Если какой-то тип умеет выполнять требуемые от него операции, стоит этот тип принимать. Иными словами, тебе необязательно принимать именно список или кортеж - тебе подойдёт любая коллекция, которую можно перечислить, и у которой есть длина.
Так что я бы посоветовал сделать иначе:
import collections.abc as abc
# Collection требует поддержки итерации, проверки вхождения и длины.
def avg(data_set: abc.Collection[int]) -> float:  # type hint для разработчика и IDE
    # если получим что-то не то, будет выкинуто TypeError - а нам это и надо.
    avg = sum(data_set) / len(data_set)  # такое деление вернёт float
    return avg

Либо, если тебе хочется своей обработки ошибки:
import collections.abc as abc

def avg(data_set: abc.Collection[int]) -> float:  # type hint для разработчика и IDE
    # протокол abc.Collection можно проверять через isinstance()
    if not isinstance(data_set, abc.Collection):  
        raise TypeError(f'data_set must be a collection of ints, got {type(data_set)}')
    avg = sum(data_set) / len(data_set)  # такое деление вернёт float
    return avg
Иными словами, может иметь смысл определить минимально необходимый тебе набор операций, и описывать как входной тип именно его. Хотя зачастую можно и не заморачиваться. Если ты значешь, что будешь передавать в функцию только список - можешь прописывать только список.

И это будет работать:
>>> avg([1,2,3])  # список
2.0  # работает
>>> avg({1,2,3})  # множество
2.0  # работает
>>> avg(x for x in range(3))  # выражение-генератор
Traceback (most recent call last):  # отказ, у генераторов нет заранее известной длины
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in avg
TypeError: data_set must be a collection of ints, got <class 'generator'>